From 62ad5f834ee483bf3d77954d3aa0638da0309a7d Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Thu, 20 Nov 2025 09:32:07 +0100 Subject: [PATCH 01/59] build: Create dockerfile without --- django.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django.Dockerfile b/django.Dockerfile index 2f2a5a4f9..f9d536c86 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -12,5 +12,5 @@ RUN apt-get update -y \ postgresql-client-common \ && rm -rf /var/lib/apt/lists/* \ && pip install poetry \ - && poetry install \ + && poetry install --without dev \ && poetry run python manage.py compilemessages From 1b5f41b4540c037b03a9e740c9910c8b0bab9b2f Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Thu, 20 Nov 2025 09:53:43 +0100 Subject: [PATCH 02/59] move drf-spectacular and icecream to prod --- poetry.lock | 321 +++++++++++++++++++++---------------------------- pyproject.toml | 4 +- 2 files changed, 142 insertions(+), 183 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2002254ec..5fc2b9eef 100644 --- a/poetry.lock +++ b/poetry.lock @@ -44,19 +44,19 @@ tests = ["mypy (>=1.14.0)", "pytest", "pytest-asyncio"] [[package]] name = "asttokens" -version = "3.0.0" +version = "3.0.1" description = "Annotate AST trees with source code positions" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] files = [ - {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, - {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, + {file = "asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a"}, + {file = "asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7"}, ] [package.extras] -astroid = ["astroid (>=2,<4)"] -test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] +astroid = ["astroid (>=2,<5)"] +test = ["astroid (>=2,<5)", "pytest (<9.0)", "pytest-cov", "pytest-xdist"] [[package]] name = "attrs" @@ -64,7 +64,7 @@ version = "25.4.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, @@ -761,7 +761,6 @@ 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\""} [[package]] name = "contourpy" @@ -1452,7 +1451,7 @@ version = "3.16.1" description = "Web APIs for Django, made easy." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main"] files = [ {file = "djangorestframework-3.16.1-py3-none-any.whl", hash = "sha256:33a59f47fb9c85ede792cbf88bde71893bcda0667bc573f784649521f1102cec"}, {file = "djangorestframework-3.16.1.tar.gz", hash = "sha256:166809528b1aced0a17dc66c24492af18049f2c9420dbd0be29422029cfc3ff7"}, @@ -1506,14 +1505,14 @@ tqdm = ">=4.62.2" [[package]] name = "drf-spectacular" -version = "0.28.0" +version = "0.29.0" description = "Sane and flexible OpenAPI 3 schema generation for Django REST framework" optional = false python-versions = ">=3.7" -groups = ["dev"] +groups = ["main"] files = [ - {file = "drf_spectacular-0.28.0-py3-none-any.whl", hash = "sha256:856e7edf1056e49a4245e87a61e8da4baff46c83dbc25be1da2df77f354c7cb4"}, - {file = "drf_spectacular-0.28.0.tar.gz", hash = "sha256:2c778a47a40ab2f5078a7c42e82baba07397bb35b074ae4680721b2805943061"}, + {file = "drf_spectacular-0.29.0-py3-none-any.whl", hash = "sha256:d1ee7c9535d89848affb4427347f7c4a22c5d22530b8842ef133d7b72e19b41a"}, + {file = "drf_spectacular-0.29.0.tar.gz", hash = "sha256:0a069339ea390ce7f14a75e8b5af4a0860a46e833fd4af027411a3e94fc1a0cc"}, ] [package.dependencies] @@ -1549,7 +1548,7 @@ version = "2.2.1" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] files = [ {file = "executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017"}, {file = "executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4"}, @@ -1763,7 +1762,7 @@ version = "2.1.8" 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"] +groups = ["main"] files = [ {file = "icecream-2.1.8-py3-none-any.whl", hash = "sha256:10b1c39dcb54cb28eb487bac56c35dbf9c2b2f406d24340e1a615c3f17274852"}, {file = "icecream-2.1.8.tar.gz", hash = "sha256:37269bbc62b02f0d85bfaf3a0eb4df272c967fad059f7ddcdaee5303ea2b2a62"}, @@ -1811,7 +1810,7 @@ version = "0.5.1" description = "A port of Ruby on Rails inflector to Python" optional = false python-versions = ">=3.5" -groups = ["dev"] +groups = ["main"] files = [ {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"}, {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, @@ -1878,7 +1877,7 @@ version = "4.25.1" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main"] files = [ {file = "jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63"}, {file = "jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85"}, @@ -1900,7 +1899,7 @@ version = "2025.9.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main"] files = [ {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, @@ -2932,7 +2931,7 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -3212,7 +3211,7 @@ version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -3314,7 +3313,7 @@ version = "0.37.0" description = "JSON Referencing + Python" optional = false python-versions = ">=3.10" -groups = ["dev"] +groups = ["main"] files = [ {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, @@ -3473,167 +3472,127 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rpds-py" -version = "0.27.1" +version = "0.29.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false -python-versions = ">=3.9" -groups = ["dev"] +python-versions = ">=3.10" +groups = ["main"] files = [ - {file = "rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef"}, - {file = "rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1"}, - {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10"}, - {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808"}, - {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8"}, - {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9"}, - {file = "rpds_py-0.27.1-cp310-cp310-win32.whl", hash = "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4"}, - {file = "rpds_py-0.27.1-cp310-cp310-win_amd64.whl", hash = "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1"}, - {file = "rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881"}, - {file = "rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a"}, - {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde"}, - {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21"}, - {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9"}, - {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948"}, - {file = "rpds_py-0.27.1-cp311-cp311-win32.whl", hash = "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39"}, - {file = "rpds_py-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15"}, - {file = "rpds_py-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746"}, - {file = "rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90"}, - {file = "rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a"}, - {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444"}, - {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a"}, - {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1"}, - {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998"}, - {file = "rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39"}, - {file = "rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594"}, - {file = "rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502"}, - {file = "rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b"}, - {file = "rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d"}, - {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274"}, - {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd"}, - {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2"}, - {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002"}, - {file = "rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3"}, - {file = "rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83"}, - {file = "rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d"}, - {file = "rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228"}, - {file = "rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21"}, - {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef"}, - {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081"}, - {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd"}, - {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7"}, - {file = "rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688"}, - {file = "rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797"}, - {file = "rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334"}, - {file = "rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9"}, - {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60"}, - {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e"}, - {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212"}, - {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675"}, - {file = "rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3"}, - {file = "rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456"}, - {file = "rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3"}, - {file = "rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2"}, - {file = "rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48"}, - {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb"}, - {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734"}, - {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb"}, - {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0"}, - {file = "rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a"}, - {file = "rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772"}, - {file = "rpds_py-0.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c918c65ec2e42c2a78d19f18c553d77319119bf43aa9e2edf7fb78d624355527"}, - {file = "rpds_py-0.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1fea2b1a922c47c51fd07d656324531adc787e415c8b116530a1d29c0516c62d"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbf94c58e8e0cd6b6f38d8de67acae41b3a515c26169366ab58bdca4a6883bb8"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2a8fed130ce946d5c585eddc7c8eeef0051f58ac80a8ee43bd17835c144c2cc"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:037a2361db72ee98d829bc2c5b7cc55598ae0a5e0ec1823a56ea99374cfd73c1"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5281ed1cc1d49882f9997981c88df1a22e140ab41df19071222f7e5fc4e72125"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fd50659a069c15eef8aa3d64bbef0d69fd27bb4a50c9ab4f17f83a16cbf8905"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:c4b676c4ae3921649a15d28ed10025548e9b561ded473aa413af749503c6737e"}, - {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:079bc583a26db831a985c5257797b2b5d3affb0386e7ff886256762f82113b5e"}, - {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4e44099bd522cba71a2c6b97f68e19f40e7d85399de899d66cdb67b32d7cb786"}, - {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e202e6d4188e53c6661af813b46c37ca2c45e497fc558bacc1a7630ec2695aec"}, - {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f41f814b8eaa48768d1bb551591f6ba45f87ac76899453e8ccd41dba1289b04b"}, - {file = "rpds_py-0.27.1-cp39-cp39-win32.whl", hash = "sha256:9e71f5a087ead99563c11fdaceee83ee982fd39cf67601f4fd66cb386336ee52"}, - {file = "rpds_py-0.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:71108900c9c3c8590697244b9519017a400d9ba26a36c48381b3f64743a44aab"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b"}, - {file = "rpds_py-0.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6"}, - {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa8933159edc50be265ed22b401125c9eebff3171f570258854dbce3ecd55475"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a50431bf02583e21bf273c71b89d710e7a710ad5e39c725b14e685610555926f"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78af06ddc7fe5cc0e967085a9115accee665fb912c22a3f54bad70cc65b05fe6"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70d0738ef8fee13c003b100c2fbd667ec4f133468109b3472d249231108283a3"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2f6fd8a1cea5bbe599b6e78a6e5ee08db434fc8ffea51ff201c8765679698b3"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8177002868d1426305bb5de1e138161c2ec9eb2d939be38291d7c431c4712df8"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:008b839781d6c9bf3b6a8984d1d8e56f0ec46dc56df61fd669c49b58ae800400"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:a55b9132bb1ade6c734ddd2759c8dc132aa63687d259e725221f106b83a0e485"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a46fdec0083a26415f11d5f236b79fa1291c32aaa4a17684d82f7017a1f818b1"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8a63b640a7845f2bdd232eb0d0a4a2dd939bcdd6c57e6bb134526487f3160ec5"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7e32721e5d4922deaaf963469d795d5bde6093207c52fec719bd22e5d1bedbc4"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2c426b99a068601b5f4623573df7a7c3d72e87533a2dd2253353a03e7502566c"}, - {file = "rpds_py-0.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4fc9b7fe29478824361ead6e14e4f5aed570d477e06088826537e202d25fe859"}, - {file = "rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8"}, + {file = "rpds_py-0.29.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4ae4b88c6617e1b9e5038ab3fccd7bac0842fdda2b703117b2aa99bc85379113"}, + {file = "rpds_py-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7d9128ec9d8cecda6f044001fde4fb71ea7c24325336612ef8179091eb9596b9"}, + {file = "rpds_py-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37812c3da8e06f2bb35b3cf10e4a7b68e776a706c13058997238762b4e07f4f"}, + {file = "rpds_py-0.29.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:66786c3fb1d8de416a7fa8e1cb1ec6ba0a745b2b0eee42f9b7daa26f1a495545"}, + {file = "rpds_py-0.29.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58f5c77f1af888b5fd1876c9a0d9858f6f88a39c9dd7c073a88e57e577da66d"}, + {file = "rpds_py-0.29.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:799156ef1f3529ed82c36eb012b5d7a4cf4b6ef556dd7cc192148991d07206ae"}, + {file = "rpds_py-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:453783477aa4f2d9104c4b59b08c871431647cb7af51b549bbf2d9eb9c827756"}, + {file = "rpds_py-0.29.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:24a7231493e3c4a4b30138b50cca089a598e52c34cf60b2f35cebf62f274fdea"}, + {file = "rpds_py-0.29.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7033c1010b1f57bb44d8067e8c25aa6fa2e944dbf46ccc8c92b25043839c3fd2"}, + {file = "rpds_py-0.29.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0248b19405422573621172ab8e3a1f29141362d13d9f72bafa2e28ea0cdca5a2"}, + {file = "rpds_py-0.29.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f9f436aee28d13b9ad2c764fc273e0457e37c2e61529a07b928346b219fcde3b"}, + {file = "rpds_py-0.29.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24a16cb7163933906c62c272de20ea3c228e4542c8c45c1d7dc2b9913e17369a"}, + {file = "rpds_py-0.29.0-cp310-cp310-win32.whl", hash = "sha256:1a409b0310a566bfd1be82119891fefbdce615ccc8aa558aff7835c27988cbef"}, + {file = "rpds_py-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5523b0009e7c3c1263471b69d8da1c7d41b3ecb4cb62ef72be206b92040a950"}, + {file = "rpds_py-0.29.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9b9c764a11fd637e0322a488560533112837f5334ffeb48b1be20f6d98a7b437"}, + {file = "rpds_py-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fd2164d73812026ce970d44c3ebd51e019d2a26a4425a5dcbdfa93a34abc383"}, + {file = "rpds_py-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a097b7f7f7274164566ae90a221fd725363c0e9d243e2e9ed43d195ccc5495c"}, + {file = "rpds_py-0.29.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cdc0490374e31cedefefaa1520d5fe38e82fde8748cbc926e7284574c714d6b"}, + {file = "rpds_py-0.29.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89ca2e673ddd5bde9b386da9a0aac0cab0e76f40c8f0aaf0d6311b6bbf2aa311"}, + {file = "rpds_py-0.29.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5d9da3ff5af1ca1249b1adb8ef0573b94c76e6ae880ba1852f033bf429d4588"}, + {file = "rpds_py-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8238d1d310283e87376c12f658b61e1ee23a14c0e54c7c0ce953efdbdc72deed"}, + {file = "rpds_py-0.29.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:2d6fb2ad1c36f91c4646989811e84b1ea5e0c3cf9690b826b6e32b7965853a63"}, + {file = "rpds_py-0.29.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:534dc9df211387547267ccdb42253aa30527482acb38dd9b21c5c115d66a96d2"}, + {file = "rpds_py-0.29.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d456e64724a075441e4ed648d7f154dc62e9aabff29bcdf723d0c00e9e1d352f"}, + {file = "rpds_py-0.29.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a738f2da2f565989401bd6fd0b15990a4d1523c6d7fe83f300b7e7d17212feca"}, + {file = "rpds_py-0.29.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a110e14508fd26fd2e472bb541f37c209409876ba601cf57e739e87d8a53cf95"}, + {file = "rpds_py-0.29.0-cp311-cp311-win32.whl", hash = "sha256:923248a56dd8d158389a28934f6f69ebf89f218ef96a6b216a9be6861804d3f4"}, + {file = "rpds_py-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:539eb77eb043afcc45314d1be09ea6d6cafb3addc73e0547c171c6d636957f60"}, + {file = "rpds_py-0.29.0-cp311-cp311-win_arm64.whl", hash = "sha256:bdb67151ea81fcf02d8f494703fb728d4d34d24556cbff5f417d74f6f5792e7c"}, + {file = "rpds_py-0.29.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0891cfd8db43e085c0ab93ab7e9b0c8fee84780d436d3b266b113e51e79f954"}, + {file = "rpds_py-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3897924d3f9a0361472d884051f9a2460358f9a45b1d85a39a158d2f8f1ad71c"}, + {file = "rpds_py-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21deb8e0d1571508c6491ce5ea5e25669b1dd4adf1c9d64b6314842f708b5d"}, + {file = "rpds_py-0.29.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9efe71687d6427737a0a2de9ca1c0a216510e6cd08925c44162be23ed7bed2d5"}, + {file = "rpds_py-0.29.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40f65470919dc189c833e86b2c4bd21bd355f98436a2cef9e0a9a92aebc8e57e"}, + {file = "rpds_py-0.29.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:def48ff59f181130f1a2cb7c517d16328efac3ec03951cca40c1dc2049747e83"}, + {file = "rpds_py-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad7bd570be92695d89285a4b373006930715b78d96449f686af422debb4d3949"}, + {file = "rpds_py-0.29.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:5a572911cd053137bbff8e3a52d31c5d2dba51d3a67ad902629c70185f3f2181"}, + {file = "rpds_py-0.29.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d583d4403bcbf10cffc3ab5cee23d7643fcc960dff85973fd3c2d6c86e8dbb0c"}, + {file = "rpds_py-0.29.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:070befbb868f257d24c3bb350dbd6e2f645e83731f31264b19d7231dd5c396c7"}, + {file = "rpds_py-0.29.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fc935f6b20b0c9f919a8ff024739174522abd331978f750a74bb68abd117bd19"}, + {file = "rpds_py-0.29.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8c5a8ecaa44ce2d8d9d20a68a2483a74c07f05d72e94a4dff88906c8807e77b0"}, + {file = "rpds_py-0.29.0-cp312-cp312-win32.whl", hash = "sha256:ba5e1aeaf8dd6d8f6caba1f5539cddda87d511331714b7b5fc908b6cfc3636b7"}, + {file = "rpds_py-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:b5f6134faf54b3cb83375db0f113506f8b7770785be1f95a631e7e2892101977"}, + {file = "rpds_py-0.29.0-cp312-cp312-win_arm64.whl", hash = "sha256:b016eddf00dca7944721bf0cd85b6af7f6c4efaf83ee0b37c4133bd39757a8c7"}, + {file = "rpds_py-0.29.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1585648d0760b88292eecab5181f5651111a69d90eff35d6b78aa32998886a61"}, + {file = "rpds_py-0.29.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:521807963971a23996ddaf764c682b3e46459b3c58ccd79fefbe16718db43154"}, + {file = "rpds_py-0.29.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a8896986efaa243ab713c69e6491a4138410f0fe36f2f4c71e18bd5501e8014"}, + {file = "rpds_py-0.29.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d24564a700ef41480a984c5ebed62b74e6ce5860429b98b1fede76049e953e6"}, + {file = "rpds_py-0.29.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6596b93c010d386ae46c9fba9bfc9fc5965fa8228edeac51576299182c2e31c"}, + {file = "rpds_py-0.29.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5cc58aac218826d054c7da7f95821eba94125d88be673ff44267bb89d12a5866"}, + {file = "rpds_py-0.29.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de73e40ebc04dd5d9556f50180395322193a78ec247e637e741c1b954810f295"}, + {file = "rpds_py-0.29.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:295ce5ac7f0cf69a651ea75c8f76d02a31f98e5698e82a50a5f4d4982fbbae3b"}, + {file = "rpds_py-0.29.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1ea59b23ea931d494459c8338056fe7d93458c0bf3ecc061cd03916505369d55"}, + {file = "rpds_py-0.29.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f49d41559cebd608042fdcf54ba597a4a7555b49ad5c1c0c03e0af82692661cd"}, + {file = "rpds_py-0.29.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:05a2bd42768ea988294ca328206efbcc66e220d2d9b7836ee5712c07ad6340ea"}, + {file = "rpds_py-0.29.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33ca7bdfedd83339ca55da3a5e1527ee5870d4b8369456b5777b197756f3ca22"}, + {file = "rpds_py-0.29.0-cp313-cp313-win32.whl", hash = "sha256:20c51ae86a0bb9accc9ad4e6cdeec58d5ebb7f1b09dd4466331fc65e1766aae7"}, + {file = "rpds_py-0.29.0-cp313-cp313-win_amd64.whl", hash = "sha256:6410e66f02803600edb0b1889541f4b5cc298a5ccda0ad789cc50ef23b54813e"}, + {file = "rpds_py-0.29.0-cp313-cp313-win_arm64.whl", hash = "sha256:56838e1cd9174dc23c5691ee29f1d1be9eab357f27efef6bded1328b23e1ced2"}, + {file = "rpds_py-0.29.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:37d94eadf764d16b9a04307f2ab1d7af6dc28774bbe0535c9323101e14877b4c"}, + {file = "rpds_py-0.29.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d472cf73efe5726a067dce63eebe8215b14beabea7c12606fd9994267b3cfe2b"}, + {file = "rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72fdfd5ff8992e4636621826371e3ac5f3e3b8323e9d0e48378e9c13c3dac9d0"}, + {file = "rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2549d833abdf8275c901313b9e8ff8fba57e50f6a495035a2a4e30621a2f7cc4"}, + {file = "rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4448dad428f28a6a767c3e3b80cde3446a22a0efbddaa2360f4bb4dc836d0688"}, + {file = "rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:115f48170fd4296a33938d8c11f697f5f26e0472e43d28f35624764173a60e4d"}, + {file = "rpds_py-0.29.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e5bb73ffc029820f4348e9b66b3027493ae00bca6629129cd433fd7a76308ee"}, + {file = "rpds_py-0.29.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b1581fcde18fcdf42ea2403a16a6b646f8eb1e58d7f90a0ce693da441f76942e"}, + {file = "rpds_py-0.29.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16e9da2bda9eb17ea318b4c335ec9ac1818e88922cbe03a5743ea0da9ecf74fb"}, + {file = "rpds_py-0.29.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:28fd300326dd21198f311534bdb6d7e989dd09b3418b3a91d54a0f384c700967"}, + {file = "rpds_py-0.29.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2aba991e041d031c7939e1358f583ae405a7bf04804ca806b97a5c0e0af1ea5e"}, + {file = "rpds_py-0.29.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f437026dbbc3f08c99cc41a5b2570c6e1a1ddbe48ab19a9b814254128d4ea7a"}, + {file = "rpds_py-0.29.0-cp313-cp313t-win32.whl", hash = "sha256:6e97846e9800a5d0fe7be4d008f0c93d0feeb2700da7b1f7528dabafb31dfadb"}, + {file = "rpds_py-0.29.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f49196aec7c4b406495f60e6f947ad71f317a765f956d74bbd83996b9edc0352"}, + {file = "rpds_py-0.29.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:394d27e4453d3b4d82bb85665dc1fcf4b0badc30fc84282defed71643b50e1a1"}, + {file = "rpds_py-0.29.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55d827b2ae95425d3be9bc9a5838b6c29d664924f98146557f7715e331d06df8"}, + {file = "rpds_py-0.29.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc31a07ed352e5462d3ee1b22e89285f4ce97d5266f6d1169da1142e78045626"}, + {file = "rpds_py-0.29.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c4695dd224212f6105db7ea62197144230b808d6b2bba52238906a2762f1d1e7"}, + {file = "rpds_py-0.29.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcae1770b401167f8b9e1e3f566562e6966ffa9ce63639916248a9e25fa8a244"}, + {file = "rpds_py-0.29.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:90f30d15f45048448b8da21c41703b31c61119c06c216a1bf8c245812a0f0c17"}, + {file = "rpds_py-0.29.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44a91e0ab77bdc0004b43261a4b8cd6d6b451e8d443754cfda830002b5745b32"}, + {file = "rpds_py-0.29.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:4aa195e5804d32c682e453b34474f411ca108e4291c6a0f824ebdc30a91c973c"}, + {file = "rpds_py-0.29.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7971bdb7bf4ee0f7e6f67fa4c7fbc6019d9850cc977d126904392d363f6f8318"}, + {file = "rpds_py-0.29.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8ae33ad9ce580c7a47452c3b3f7d8a9095ef6208e0a0c7e4e2384f9fc5bf8212"}, + {file = "rpds_py-0.29.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c661132ab2fb4eeede2ef69670fd60da5235209874d001a98f1542f31f2a8a94"}, + {file = "rpds_py-0.29.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bb78b3a0d31ac1bde132c67015a809948db751cb4e92cdb3f0b242e430b6ed0d"}, + {file = "rpds_py-0.29.0-cp314-cp314-win32.whl", hash = "sha256:f475f103488312e9bd4000bc890a95955a07b2d0b6e8884aef4be56132adbbf1"}, + {file = "rpds_py-0.29.0-cp314-cp314-win_amd64.whl", hash = "sha256:b9cf2359a4fca87cfb6801fae83a76aedf66ee1254a7a151f1341632acf67f1b"}, + {file = "rpds_py-0.29.0-cp314-cp314-win_arm64.whl", hash = "sha256:9ba8028597e824854f0f1733d8b964e914ae3003b22a10c2c664cb6927e0feb9"}, + {file = "rpds_py-0.29.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:e71136fd0612556b35c575dc2726ae04a1669e6a6c378f2240312cf5d1a2ab10"}, + {file = "rpds_py-0.29.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:76fe96632d53f3bf0ea31ede2f53bbe3540cc2736d4aec3b3801b0458499ef3a"}, + {file = "rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9459a33f077130dbb2c7c3cea72ee9932271fb3126404ba2a2661e4fe9eb7b79"}, + {file = "rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5c9546cfdd5d45e562cc0444b6dddc191e625c62e866bf567a2c69487c7ad28a"}, + {file = "rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12597d11d97b8f7e376c88929a6e17acb980e234547c92992f9f7c058f1a7310"}, + {file = "rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28de03cf48b8a9e6ec10318f2197b83946ed91e2891f651a109611be4106ac4b"}, + {file = "rpds_py-0.29.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd7951c964069039acc9d67a8ff1f0a7f34845ae180ca542b17dc1456b1f1808"}, + {file = "rpds_py-0.29.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:c07d107b7316088f1ac0177a7661ca0c6670d443f6fe72e836069025e6266761"}, + {file = "rpds_py-0.29.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1de2345af363d25696969befc0c1688a6cb5e8b1d32b515ef84fc245c6cddba3"}, + {file = "rpds_py-0.29.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:00e56b12d2199ca96068057e1ae7f9998ab6e99cda82431afafd32f3ec98cca9"}, + {file = "rpds_py-0.29.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3919a3bbecee589300ed25000b6944174e07cd20db70552159207b3f4bbb45b8"}, + {file = "rpds_py-0.29.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7fa2ccc312bbd91e43aa5e0869e46bc03278a3dddb8d58833150a18b0f0283a"}, + {file = "rpds_py-0.29.0-cp314-cp314t-win32.whl", hash = "sha256:97c817863ffc397f1e6a6e9d2d89fe5408c0a9922dac0329672fb0f35c867ea5"}, + {file = "rpds_py-0.29.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2023473f444752f0f82a58dfcbee040d0a1b3d1b3c2ec40e884bd25db6d117d2"}, + {file = "rpds_py-0.29.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:acd82a9e39082dc5f4492d15a6b6c8599aa21db5c35aaf7d6889aea16502c07d"}, + {file = "rpds_py-0.29.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:715b67eac317bf1c7657508170a3e011a1ea6ccb1c9d5f296e20ba14196be6b3"}, + {file = "rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3b1b87a237cb2dba4db18bcfaaa44ba4cd5936b91121b62292ff21df577fc43"}, + {file = "rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c3c3e8101bb06e337c88eb0c0ede3187131f19d97d43ea0e1c5407ea74c0cbf"}, + {file = "rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8e54d6e61f3ecd3abe032065ce83ea63417a24f437e4a3d73d2f85ce7b7cfe"}, + {file = "rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fbd4e9aebf110473a420dea85a238b254cf8a15acb04b22a5a6b5ce8925b760"}, + {file = "rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fdf53d36e6c72819993e35d1ebeeb8e8fc688d0c6c2b391b55e335b3afba5a"}, + {file = "rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:ea7173df5d86f625f8dde6d5929629ad811ed8decda3b60ae603903839ac9ac0"}, + {file = "rpds_py-0.29.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:76054d540061eda273274f3d13a21a4abdde90e13eaefdc205db37c05230efce"}, + {file = "rpds_py-0.29.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:9f84c549746a5be3bc7415830747a3a0312573afc9f95785eb35228bb17742ec"}, + {file = "rpds_py-0.29.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:0ea962671af5cb9a260489e311fa22b2e97103e3f9f0caaea6f81390af96a9ed"}, + {file = "rpds_py-0.29.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:f7728653900035fb7b8d06e1e5900545d8088efc9d5d4545782da7df03ec803f"}, + {file = "rpds_py-0.29.0.tar.gz", hash = "sha256:fe55fe686908f50154d1dc599232016e50c243b438c3b7432f24e2895b0e5359"}, ] [[package]] @@ -3925,7 +3884,7 @@ version = "4.2.0" description = "Implementation of RFC 6570 URI Templates" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main"] files = [ {file = "uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686"}, {file = "uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e"}, @@ -4263,4 +4222,4 @@ test = ["pytest"] [metadata] lock-version = "2.1" python-versions = ">=3.13,<4.0" -content-hash = "5f8bfac3e2e585004042b45c5ed0d48557eeac322ace8dcd443083a58c4f27c1" +content-hash = "9f8c77c8b2549740112849d3bfb39d1514c6fa18b1062fa30d90c65b177a8241" diff --git a/pyproject.toml b/pyproject.toml index c34dea6d1..93dd89d44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,8 @@ dependencies = [ "django-cors-headers (>=4.6.0,<5.0.0)", "distinctipy[extras] (==1.3.4)", "slack-sdk (>=3.35.0,<4.0.0)", + "drf-spectacular (>=0.29.0,<0.30.0)", + "icecream (>=2.1.8,<3.0.0)", ] [tool.poetry.group.dev.dependencies] @@ -50,11 +52,9 @@ pytest-django = "^4.11.1" pytest-sugar = "^1.1.1" selenium = "^4.37.0" factory-boy = "^3.3.3" -icecream = "^2.1.8" djlint = "^1.36.4" pypdf = "^6.1.1" django-upgrade = "^1.29.0" -drf-spectacular = "^0.28.0" parameterized = "^0.9.0" django-silk = "^5.4.3" From 3509e63cc104147e947a84f502a5fc3b41fbfd5c Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Thu, 20 Nov 2025 09:57:02 +0100 Subject: [PATCH 03/59] add dockerfile for prod --- .github/workflows/docker-build.yml | 2 +- django.prod.Dockerfile | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 django.prod.Dockerfile diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 620517e2f..c7f68a342 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -79,4 +79,4 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} no-cache: true - file: django.Dockerfile + file: django.prod.Dockerfile diff --git a/django.prod.Dockerfile b/django.prod.Dockerfile new file mode 100644 index 000000000..f9d536c86 --- /dev/null +++ b/django.prod.Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.13 +ENV PYTHONUNBUFFERED=1 +WORKDIR /app +COPY . /app + +RUN apt-get update -y \ + && apt-get --no-install-recommends install -y \ + gettext \ + libldap2-dev \ + libsasl2-dev \ + postgresql-client \ + postgresql-client-common \ + && rm -rf /var/lib/apt/lists/* \ + && pip install poetry \ + && poetry install --without dev \ + && poetry run python manage.py compilemessages From d5bf3b83af1835f26aeef7ce4794d427890dea77 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Thu, 20 Nov 2025 09:57:19 +0100 Subject: [PATCH 04/59] Revert "build: Create dockerfile without" This reverts commit 62ad5f834ee483bf3d77954d3aa0638da0309a7d. --- django.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django.Dockerfile b/django.Dockerfile index f9d536c86..2f2a5a4f9 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -12,5 +12,5 @@ RUN apt-get update -y \ postgresql-client-common \ && rm -rf /var/lib/apt/lists/* \ && pip install poetry \ - && poetry install --without dev \ + && poetry install \ && poetry run python manage.py compilemessages From 590468ea6d15a179608a60bfa5cd4a283e8a87fe Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:48:30 +0100 Subject: [PATCH 05/59] remove poetry and initiate first-attempt of multi-stage and add user... --- CONTRIBUTING.md | 12 ++--- Makefile | 10 ++-- django.Dockerfile | 74 ++++++++++++++++++++++++----- docker-compose.yml | 11 ++--- scripts/generate_api_schema.sh | 2 +- scripts/update_translation_files.sh | 4 +- 6 files changed, 80 insertions(+), 33 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc14afa39..a3a91f4c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -111,7 +111,7 @@ The class name is the convention for the word in texts, followed by how to write ### Django Shell ```sh -docker compose exec web poetry run python manage.py shell_plus +docker compose exec web python manage.py shell_plus ``` ### LDAP @@ -129,7 +129,7 @@ docker compose up -d openldap Then, run the tests. ```sh -docker compose run --rm web poetry run pytest +docker compose run --rm web pytest ``` The `--rm` option will delete the temporary containers created to run the tests. Omit it if you want to keep the @@ -145,8 +145,8 @@ secret To generate the translation files, first use "makemessages" and specify the language you want to generate: ```sh -docker compose exec -w /app/tapir web poetry run python ../manage.py makemessages --no-wrap -l de -docker compose run --rm -w /app web poetry run python manage.py makemessages --no-wrap -l de -d djangojs +docker compose exec -w /app/tapir web python ../manage.py makemessages --no-wrap -l de +docker compose run --rm -w /app web python manage.py makemessages --no-wrap -l de -d djangojs ``` Update tapir/translations/locale/de/LC_MESSAGES/django.po with your translations. @@ -180,7 +180,7 @@ All changes must be done in the docker container. Since our development environm docker container, you must run djangos makemigrations on docker. You can do this with this command: ```sh -docker compose exec web poetry run python manage.py makemigrations +docker compose exec web python manage.py makemigrations ``` Please check the migration script. It might contain unwished changes. There seems to be a bug in ldpa migrations. @@ -190,7 +190,7 @@ Please check the migration script. It might contain unwished changes. There seem Last step is to update the database. this is done with this command: ```sh -docker compose exec web poetry run python manage.py migrate +docker compose exec web python manage.py migrate ``` Please check, if applications runs (again). diff --git a/Makefile b/Makefile index 9098886bd..506e893fa 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,17 @@ lint: - poetry run black . + black . check-formatting: - poetry run black --check . + black --check . test: - poetry run pytest --cov-report xml:coverage.xml --cov=tapir --cov-config=pyproject.toml + pytest --cov-report xml:coverage.xml --cov=tapir --cov-config=pyproject.toml check-translations: cp tapir/translations/locale/de/LC_MESSAGES/django.po tapir/translations/locale/de/LC_MESSAGES/django-old.po - cd tapir && poetry run python ../manage.py makemessages --no-wrap -l de + cd tapir && python ../manage.py makemessages --no-wrap -l de git diff --ignore-matching-lines=POT-Creation-Date --exit-code --no-index tapir/translations/locale/de/LC_MESSAGES/django.po tapir/translations/locale/de/LC_MESSAGES/django-old.po rm tapir/translations/locale/de/LC_MESSAGES/django-old.po check-migrations: - poetry run python manage.py makemigrations --check + python manage.py makemigrations --check diff --git a/django.Dockerfile b/django.Dockerfile index 2f2a5a4f9..69e5d44d7 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -1,16 +1,66 @@ -FROM python:3.13 -ENV PYTHONUNBUFFERED=1 -WORKDIR /app -COPY . /app +# syntax=docker/dockerfile:1 + +FROM python:3.13-slim AS build + +ENV POETRY_VERSION=2.2.1 \ + PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + PIP_NO_CACHE_DIR=off \ + PIP_DISABLE_PIP_VERSION_CHECK=on \ + PIP_DEFAULT_TIMEOUT=100 \ + POETRY_HOME="/opt/poetry" \ + POETRY_VIRTUALENVS_IN_PROJECT=true \ + POETRY_NO_INTERACTION=1 \ + PYSETUP_PATH="/opt/pysetup" \ + VENV_PATH="/opt/pysetup/.venv" + +ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH" + +RUN pip install "poetry==$POETRY_VERSION" +RUN apt-get update \ + && apt-get install --no-install-recommends -y \ + build-essential \ + # psycopg2 dependencies + && apt-get install -y libpq-dev \ + # Translations dependencies + && apt-get install -y gettext \ + libldap2-dev \ + libsasl2-dev \ + postgresql-client \ + postgresql-client-common \ + # cleaning up unused files + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ + && rm -rf /var/lib/apt/lists/* +WORKDIR $PYSETUP_PATH -RUN apt-get update -y \ - && apt-get --no-install-recommends install -y \ - gettext \ - libldap2-dev \ +COPY poetry.lock pyproject.toml ./ + +RUN --mount=type=cache,target=/root/.cache/pypoetry \ + poetry install --no-root + +COPY . . + +FROM python:3.13 AS runtime + +ENV VENV_PATH="/opt/pysetup/.venv" \ + PATH="/opt/pysetup/.venv/bin:$PATH" + +RUN apt-get update \ + && apt-get install --no-install-recommends -y \ + && apt-get install -y gettext \ + libldap2-dev \ libsasl2-dev \ postgresql-client \ postgresql-client-common \ - && rm -rf /var/lib/apt/lists/* \ - && pip install poetry \ - && poetry install \ - && poetry run python manage.py compilemessages + # cleaning up unused files + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ + && rm -rf /var/lib/apt/lists/* +RUN groupadd -g 1001 appgroup && \ + useradd -u 1001 -g appgroup -m -d /home/appuser -s /bin/bash appuser + +WORKDIR /app + +COPY --from=build --chown=appuser:appgroup /opt/pysetup/.venv /opt/pysetup/.venv +COPY --from=build --chown=appuser:appgroup /opt/pysetup/ ./ +COPY --chown=appuser:appgroup . . +USER appuser \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 325bc90c4..3dc4e5851 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,9 +17,8 @@ services: build: context: . dockerfile: ./django.Dockerfile - command: bash -c "poetry install && - poetry run python manage.py compilemessages --ignore \".venv\" && - poetry run python manage.py runserver_plus 0.0.0.0:80" + command: bash -c "python manage.py compilemessages --ignore \".venv\" && + python manage.py runserver_plus 0.0.0.0:80" volumes: - .:/app environment: @@ -67,8 +66,7 @@ services: celery: extends: service: web - command: bash -c "poetry install && - poetry run celery -A tapir worker -l info" + command: bash -c "celery -A tapir worker -l info" depends_on: - redis @@ -76,8 +74,7 @@ services: extends: service: web # --schedule to avoid polluting the app directory - command: bash -c "poetry install && - poetry run celery -A tapir beat -l info --schedule /tmp/celerybeat-schedule" + command: bash -c "celery -A tapir beat -l info --schedule /tmp/celerybeat-schedule" depends_on: - redis diff --git a/scripts/generate_api_schema.sh b/scripts/generate_api_schema.sh index 8c8c07dda..a96e2e820 100755 --- a/scripts/generate_api_schema.sh +++ b/scripts/generate_api_schema.sh @@ -1,3 +1,3 @@ #!/bin/sh -docker compose run --rm web poetry run python ./manage.py spectacular --file schema.yml \ No newline at end of file +docker compose run --rm web python ./manage.py spectacular --file schema.yml \ No newline at end of file diff --git a/scripts/update_translation_files.sh b/scripts/update_translation_files.sh index ac764c7b9..36cd3031c 100755 --- a/scripts/update_translation_files.sh +++ b/scripts/update_translation_files.sh @@ -1,8 +1,8 @@ #!/bin/sh # translations from Python -docker compose run --rm -w /app/tapir web poetry run python ../manage.py makemessages --no-wrap -l de +docker compose run --rm -w /app/tapir web python ../manage.py makemessages --no-wrap -l de # translations from Javascript 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 +docker compose run --rm -w /app web python manage.py makemessages --no-wrap -l de -d djangojs \ No newline at end of file From 8b3d1c1b80ea168ae3b04232fe6d5ca8373cab74 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Mon, 24 Nov 2025 21:22:55 +0100 Subject: [PATCH 06/59] linux reduce installed packages --- django.Dockerfile | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/django.Dockerfile b/django.Dockerfile index 69e5d44d7..ece64a3c7 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -21,13 +21,11 @@ RUN apt-get update \ && apt-get install --no-install-recommends -y \ build-essential \ # psycopg2 dependencies - && apt-get install -y libpq-dev \ + libpq-dev \ # Translations dependencies - && apt-get install -y gettext \ - libldap2-dev \ - libsasl2-dev \ - postgresql-client \ - postgresql-client-common \ + gettext \ + # LDAP dependencies + libldap2-dev libsasl2-dev \ # cleaning up unused files && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* @@ -47,11 +45,12 @@ ENV VENV_PATH="/opt/pysetup/.venv" \ RUN apt-get update \ && apt-get install --no-install-recommends -y \ - && apt-get install -y gettext \ - libldap2-dev \ - libsasl2-dev \ - postgresql-client \ - postgresql-client-common \ + # psycopg2 dependencies + libpq-dev \ + # Translations dependencies + gettext \ + # LDAP dependencies + libldap2-dev libsasl2-dev \ # cleaning up unused files && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* From a14ec3c7d288389805cb13ff17e50f9c0c93383c Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Mon, 24 Nov 2025 21:59:12 +0100 Subject: [PATCH 07/59] comments --- django.Dockerfile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/django.Dockerfile b/django.Dockerfile index ece64a3c7..9aa17f2ee 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -4,13 +4,19 @@ FROM python:3.13-slim AS build ENV POETRY_VERSION=2.2.1 \ PYTHONUNBUFFERED=1 \ + # prevents python creating .pyc files PYTHONDONTWRITEBYTECODE=1 \ PIP_NO_CACHE_DIR=off \ PIP_DISABLE_PIP_VERSION_CHECK=on \ PIP_DEFAULT_TIMEOUT=100 \ + # make poetry install to this location POETRY_HOME="/opt/poetry" \ + # make poetry create the virtual environment in the project's root + # it gets named `.venv` POETRY_VIRTUALENVS_IN_PROJECT=true \ + # do not ask any interactive question POETRY_NO_INTERACTION=1 \ + # this is where our requirements + virtual environment will live PYSETUP_PATH="/opt/pysetup" \ VENV_PATH="/opt/pysetup/.venv" From 5548afddd068b8289656ac570d7e71330026448d Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Mon, 24 Nov 2025 22:02:40 +0100 Subject: [PATCH 08/59] use python-slim with weasyprint-dependencies --- django.Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/django.Dockerfile b/django.Dockerfile index 9aa17f2ee..83c35227e 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -44,7 +44,7 @@ RUN --mount=type=cache,target=/root/.cache/pypoetry \ COPY . . -FROM python:3.13 AS runtime +FROM python:3.13-slim AS runtime ENV VENV_PATH="/opt/pysetup/.venv" \ PATH="/opt/pysetup/.venv/bin:$PATH" @@ -57,6 +57,8 @@ RUN apt-get update \ gettext \ # LDAP dependencies libldap2-dev libsasl2-dev \ + # weasyprint + libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0 \ # cleaning up unused files && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* From cef345a86fbad14ac8c90854a332e7bac6343279 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Wed, 26 Nov 2025 18:12:40 +0100 Subject: [PATCH 09/59] set DEV in Dockerfile to decide whether to install dev-dependencies or not --- django.Dockerfile | 10 ++++++---- django.prod.Dockerfile | 16 ---------------- docker-compose.yml | 2 ++ 3 files changed, 8 insertions(+), 20 deletions(-) delete mode 100644 django.prod.Dockerfile diff --git a/django.Dockerfile b/django.Dockerfile index 83c35227e..c54f2c056 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 FROM python:3.13-slim AS build - +ARG DEV=false ENV POETRY_VERSION=2.2.1 \ PYTHONUNBUFFERED=1 \ # prevents python creating .pyc files @@ -39,9 +39,11 @@ WORKDIR $PYSETUP_PATH COPY poetry.lock pyproject.toml ./ -RUN --mount=type=cache,target=/root/.cache/pypoetry \ - poetry install --no-root - +RUN if [ "$DEV" = "true" ]; then \ + poetry install --with dev --no-root; \ + else \ + poetry install --without dev --no-root; \ + fi COPY . . FROM python:3.13-slim AS runtime diff --git a/django.prod.Dockerfile b/django.prod.Dockerfile deleted file mode 100644 index f9d536c86..000000000 --- a/django.prod.Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM python:3.13 -ENV PYTHONUNBUFFERED=1 -WORKDIR /app -COPY . /app - -RUN apt-get update -y \ - && apt-get --no-install-recommends install -y \ - gettext \ - libldap2-dev \ - libsasl2-dev \ - postgresql-client \ - postgresql-client-common \ - && rm -rf /var/lib/apt/lists/* \ - && pip install poetry \ - && poetry install --without dev \ - && poetry run python manage.py compilemessages diff --git a/docker-compose.yml b/docker-compose.yml index 3dc4e5851..1c4c7d5f5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,8 @@ services: build: context: . dockerfile: ./django.Dockerfile + args: + DEV: true command: bash -c "python manage.py compilemessages --ignore \".venv\" && python manage.py runserver_plus 0.0.0.0:80" volumes: From 629e4f04d9be2b138c754f2290f7370104441a40 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Wed, 26 Nov 2025 18:32:15 +0100 Subject: [PATCH 10/59] install poetry the curl-way (more recommended) --- django.Dockerfile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/django.Dockerfile b/django.Dockerfile index c54f2c056..1f6bb11c3 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -22,10 +22,10 @@ ENV POETRY_VERSION=2.2.1 \ ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH" -RUN pip install "poetry==$POETRY_VERSION" + RUN apt-get update \ && apt-get install --no-install-recommends -y \ - build-essential \ + build-essential curl \ # psycopg2 dependencies libpq-dev \ # Translations dependencies @@ -35,6 +35,10 @@ RUN apt-get update \ # cleaning up unused files && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* + +RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=$POETRY_HOME python3 - && \ + ln -s $POETRY_HOME/bin/poetry /usr/local/bin/poetry + WORKDIR $PYSETUP_PATH COPY poetry.lock pyproject.toml ./ From 16b6f711a6bf5e5fa55b505e6b7f95bc38ef5743 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Wed, 26 Nov 2025 18:32:27 +0100 Subject: [PATCH 11/59] remove unneeded COPY --- django.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django.Dockerfile b/django.Dockerfile index 1f6bb11c3..7ae69f1dd 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -48,7 +48,7 @@ RUN if [ "$DEV" = "true" ]; then \ else \ poetry install --without dev --no-root; \ fi -COPY . . + FROM python:3.13-slim AS runtime From 8f1735df3721b6baf986a45ea1e93b24b240713e Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Wed, 26 Nov 2025 18:32:53 +0100 Subject: [PATCH 12/59] smaller apt-get in second layer --- django.Dockerfile | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/django.Dockerfile b/django.Dockerfile index 7ae69f1dd..14b31e63b 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -56,18 +56,11 @@ ENV VENV_PATH="/opt/pysetup/.venv" \ PATH="/opt/pysetup/.venv/bin:$PATH" RUN apt-get update \ - && apt-get install --no-install-recommends -y \ - # psycopg2 dependencies - libpq-dev \ - # Translations dependencies - gettext \ - # LDAP dependencies - libldap2-dev libsasl2-dev \ - # weasyprint + && apt-get install --no-install-recommends -y libpq-dev gettext libldap2-dev libsasl2-dev \ libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0 \ - # cleaning up unused files && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ - && rm -rf /var/lib/apt/lists/* + && rm -rf /var/lib/apt/lists/* \ + RUN groupadd -g 1001 appgroup && \ useradd -u 1001 -g appgroup -m -d /home/appuser -s /bin/bash appuser From 6e3639444bf11b8e9e17bd692f370c448e744b35 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:57:41 +0100 Subject: [PATCH 13/59] remove \ --- django.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django.Dockerfile b/django.Dockerfile index 14b31e63b..cbab38461 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -59,7 +59,7 @@ RUN apt-get update \ && apt-get install --no-install-recommends -y libpq-dev gettext libldap2-dev libsasl2-dev \ libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0 \ && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ - && rm -rf /var/lib/apt/lists/* \ + && rm -rf /var/lib/apt/lists/* RUN groupadd -g 1001 appgroup && \ useradd -u 1001 -g appgroup -m -d /home/appuser -s /bin/bash appuser From 3a823cbbeb10cef4e1b72f1d096d6cdc75c48ff9 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:59:24 +0100 Subject: [PATCH 14/59] remove obsolete prod reference --- .github/workflows/docker-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index c7f68a342..620517e2f 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -79,4 +79,4 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} no-cache: true - file: django.prod.Dockerfile + file: django.Dockerfile From c64a61ec0b050e2f4ce9696960168624ec3d312b Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Wed, 17 Dec 2025 08:50:40 +0100 Subject: [PATCH 15/59] compilemessages in dockerfile --- django.Dockerfile | 3 +++ docker-compose.yml | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/django.Dockerfile b/django.Dockerfile index cbab38461..a625f57c7 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -69,4 +69,7 @@ WORKDIR /app COPY --from=build --chown=appuser:appgroup /opt/pysetup/.venv /opt/pysetup/.venv COPY --from=build --chown=appuser:appgroup /opt/pysetup/ ./ COPY --chown=appuser:appgroup . . + +RUN python manage.py compilemessages + USER appuser \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 1c4c7d5f5..c64a89e97 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,8 +19,8 @@ services: dockerfile: ./django.Dockerfile args: DEV: true - command: bash -c "python manage.py compilemessages --ignore \".venv\" && - python manage.py runserver_plus 0.0.0.0:80" + command: bash -c "python manage.py runserver_plus 0.0.0.0:80" + user: "1001:1001" volumes: - .:/app environment: From 01fd77f6ec5ef05c59747ef7830337aa527d757a Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 10:27:50 +0100 Subject: [PATCH 16/59] poetry lock --- poetry.lock | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8f857c440..0e10324c4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. [[package]] name = "amqp" @@ -48,7 +48,7 @@ version = "3.0.0" description = "Annotate AST trees with source code positions" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] files = [ {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, @@ -64,7 +64,7 @@ version = "25.4.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, @@ -767,7 +767,6 @@ 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\""} [[package]] name = "contourpy" @@ -1458,7 +1457,7 @@ version = "3.16.1" description = "Web APIs for Django, made easy." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main"] files = [ {file = "djangorestframework-3.16.1-py3-none-any.whl", hash = "sha256:33a59f47fb9c85ede792cbf88bde71893bcda0667bc573f784649521f1102cec"}, {file = "djangorestframework-3.16.1.tar.gz", hash = "sha256:166809528b1aced0a17dc66c24492af18049f2c9420dbd0be29422029cfc3ff7"}, @@ -1512,14 +1511,14 @@ tqdm = ">=4.62.2" [[package]] name = "drf-spectacular" -version = "0.28.0" +version = "0.29.0" description = "Sane and flexible OpenAPI 3 schema generation for Django REST framework" optional = false python-versions = ">=3.7" -groups = ["dev"] +groups = ["main"] files = [ - {file = "drf_spectacular-0.28.0-py3-none-any.whl", hash = "sha256:856e7edf1056e49a4245e87a61e8da4baff46c83dbc25be1da2df77f354c7cb4"}, - {file = "drf_spectacular-0.28.0.tar.gz", hash = "sha256:2c778a47a40ab2f5078a7c42e82baba07397bb35b074ae4680721b2805943061"}, + {file = "drf_spectacular-0.29.0-py3-none-any.whl", hash = "sha256:d1ee7c9535d89848affb4427347f7c4a22c5d22530b8842ef133d7b72e19b41a"}, + {file = "drf_spectacular-0.29.0.tar.gz", hash = "sha256:0a069339ea390ce7f14a75e8b5af4a0860a46e833fd4af027411a3e94fc1a0cc"}, ] [package.dependencies] @@ -1555,7 +1554,7 @@ version = "2.2.1" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] files = [ {file = "executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017"}, {file = "executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4"}, @@ -1784,7 +1783,7 @@ version = "2.1.8" 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"] +groups = ["main"] files = [ {file = "icecream-2.1.8-py3-none-any.whl", hash = "sha256:10b1c39dcb54cb28eb487bac56c35dbf9c2b2f406d24340e1a615c3f17274852"}, {file = "icecream-2.1.8.tar.gz", hash = "sha256:37269bbc62b02f0d85bfaf3a0eb4df272c967fad059f7ddcdaee5303ea2b2a62"}, @@ -1832,7 +1831,7 @@ version = "0.5.1" description = "A port of Ruby on Rails inflector to Python" optional = false python-versions = ">=3.5" -groups = ["dev"] +groups = ["main"] files = [ {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"}, {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, @@ -1899,7 +1898,7 @@ version = "4.25.1" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main"] files = [ {file = "jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63"}, {file = "jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85"}, @@ -1921,7 +1920,7 @@ version = "2025.9.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main"] files = [ {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, @@ -2959,7 +2958,7 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -3294,7 +3293,7 @@ version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -3396,7 +3395,7 @@ version = "0.37.0" description = "JSON Referencing + Python" optional = false python-versions = ">=3.10" -groups = ["dev"] +groups = ["main"] files = [ {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, @@ -3559,7 +3558,7 @@ version = "0.27.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main"] files = [ {file = "rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef"}, {file = "rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be"}, @@ -4007,7 +4006,7 @@ version = "4.2.0" description = "Implementation of RFC 6570 URI Templates" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main"] files = [ {file = "uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686"}, {file = "uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e"}, @@ -4345,4 +4344,4 @@ test = ["pytest"] [metadata] lock-version = "2.1" python-versions = ">=3.13,<4.0" -content-hash = "84ea7d4c2ed6f7bfa4c87ff7a9eb33cd69d7134527bc9e586eac2dd214885bed" +content-hash = "0d2ea3b5a51299c41b3c334444e5797bace4f31ae5737835b4158cb18118aa04" From 97dc198be423007ed8249a4a6eddf73698e53da1 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 14:41:30 +0100 Subject: [PATCH 17/59] use uid 1000 --- django.Dockerfile | 18 ++++++++++++------ docker-compose.yml | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/django.Dockerfile b/django.Dockerfile index a625f57c7..a055c6deb 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -51,6 +51,9 @@ RUN if [ "$DEV" = "true" ]; then \ FROM python:3.13-slim AS runtime +ARG USER_UID=1000 +ARG USER_GID=$USER_UID +ARG USERNAME=tapiruser ENV VENV_PATH="/opt/pysetup/.venv" \ PATH="/opt/pysetup/.venv/bin:$PATH" @@ -61,15 +64,18 @@ RUN apt-get update \ && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* -RUN groupadd -g 1001 appgroup && \ - useradd -u 1001 -g appgroup -m -d /home/appuser -s /bin/bash appuser +RUN groupadd --gid $USER_GID $USERNAME && \ + useradd --uid $USER_UID --gid $USER_GID -m $USERNAME && \ + mkdir -p /app \ + +USER $USERNAME WORKDIR /app -COPY --from=build --chown=appuser:appgroup /opt/pysetup/.venv /opt/pysetup/.venv -COPY --from=build --chown=appuser:appgroup /opt/pysetup/ ./ -COPY --chown=appuser:appgroup . . +COPY --from=build --chown=$USERNAME:$USERNAME /opt/pysetup/.venv /opt/pysetup/.venv +COPY --from=build --chown=$USERNAME:$USERNAME /opt/pysetup/ ./ +COPY --chown=$USERNAME:$USERNAME . . RUN python manage.py compilemessages -USER appuser \ No newline at end of file + diff --git a/docker-compose.yml b/docker-compose.yml index c64a89e97..1241b2b7e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: args: DEV: true command: bash -c "python manage.py runserver_plus 0.0.0.0:80" - user: "1001:1001" + user: "1000:1000" volumes: - .:/app environment: From ca31ccb2b199dc4964679c3fc3816e72a1b4c578 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 14:41:46 +0100 Subject: [PATCH 18/59] have git installed in container --- Makefile | 2 +- django.Dockerfile | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 506e893fa..6eabec9ea 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ test: check-translations: cp tapir/translations/locale/de/LC_MESSAGES/django.po tapir/translations/locale/de/LC_MESSAGES/django-old.po - cd tapir && python ../manage.py makemessages --no-wrap -l de + python manage.py makemessages --no-wrap -l de git diff --ignore-matching-lines=POT-Creation-Date --exit-code --no-index tapir/translations/locale/de/LC_MESSAGES/django.po tapir/translations/locale/de/LC_MESSAGES/django-old.po rm tapir/translations/locale/de/LC_MESSAGES/django-old.po diff --git a/django.Dockerfile b/django.Dockerfile index a055c6deb..f6b7bf898 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -61,6 +61,7 @@ ENV VENV_PATH="/opt/pysetup/.venv" \ RUN apt-get update \ && apt-get install --no-install-recommends -y libpq-dev gettext libldap2-dev libsasl2-dev \ libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0 \ + make git \ && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* From 47184be98be5b1ec246577a6edea0399b6258ef0 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 14:42:07 +0100 Subject: [PATCH 19/59] update translation --- tapir/translations/locale/de/LC_MESSAGES/django.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tapir/translations/locale/de/LC_MESSAGES/django.po b/tapir/translations/locale/de/LC_MESSAGES/django.po index ea823cfd5..e2b43975e 100644 --- a/tapir/translations/locale/de/LC_MESSAGES/django.po +++ b/tapir/translations/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-01-29 10:22+0100\n" +"POT-Creation-Date: 2026-02-01 14:40+0100\n" "PO-Revision-Date: 2025-07-07 13:06+0000\n" "Last-Translator: Weblate Admin \n" "Language-Team: German \n" From 17716dbd053bac0b320620d7feb97ca98ccaf81c Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 14:47:28 +0100 Subject: [PATCH 20/59] fix: reintroduce cd --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6eabec9ea..506e893fa 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ test: check-translations: cp tapir/translations/locale/de/LC_MESSAGES/django.po tapir/translations/locale/de/LC_MESSAGES/django-old.po - python manage.py makemessages --no-wrap -l de + cd tapir && python ../manage.py makemessages --no-wrap -l de git diff --ignore-matching-lines=POT-Creation-Date --exit-code --no-index tapir/translations/locale/de/LC_MESSAGES/django.po tapir/translations/locale/de/LC_MESSAGES/django-old.po rm tapir/translations/locale/de/LC_MESSAGES/django-old.po From 4af8d730b274b945fe4862cd9f20a1eff46aad07 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 14:48:58 +0100 Subject: [PATCH 21/59] test if adding user 1000 to compose-run helps --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index dd99d92fd..851ebd916 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -12,6 +12,6 @@ jobs: - name: Check formatting run: docker compose run web make check-formatting - name: Check translations - run: docker compose run web make check-translations + run: docker compose run --user=1000:1000 web make check-translations - name: Check migrations run: docker compose run web make check-migrations \ No newline at end of file From 7c7f53ded3e41459ecf82c87ecf5f00ee2370cc9 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 14:54:47 +0100 Subject: [PATCH 22/59] temp add ls -la --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 506e893fa..2a626bd07 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ test: pytest --cov-report xml:coverage.xml --cov=tapir --cov-config=pyproject.toml check-translations: + ls -la /app cp tapir/translations/locale/de/LC_MESSAGES/django.po tapir/translations/locale/de/LC_MESSAGES/django-old.po cd tapir && python ../manage.py makemessages --no-wrap -l de git diff --ignore-matching-lines=POT-Creation-Date --exit-code --no-index tapir/translations/locale/de/LC_MESSAGES/django.po tapir/translations/locale/de/LC_MESSAGES/django-old.po From 70bab2bd8e937e68a3c692ddc6e8242e5cd9b1b1 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 15:01:19 +0100 Subject: [PATCH 23/59] temp add ls -la 2 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2a626bd07..0d5bd82b4 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ test: pytest --cov-report xml:coverage.xml --cov=tapir --cov-config=pyproject.toml check-translations: - ls -la /app + ls -la /app cp tapir/translations/locale/de/LC_MESSAGES/django.po tapir/translations/locale/de/LC_MESSAGES/django-old.po cd tapir && python ../manage.py makemessages --no-wrap -l de git diff --ignore-matching-lines=POT-Creation-Date --exit-code --no-index tapir/translations/locale/de/LC_MESSAGES/django.po tapir/translations/locale/de/LC_MESSAGES/django-old.po From 23c804dd1bb6c5335fe21172e66409ef1a07e733 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 15:09:08 +0100 Subject: [PATCH 24/59] $(id -u):$(id -g) --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 851ebd916..03b832551 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -12,6 +12,6 @@ jobs: - name: Check formatting run: docker compose run web make check-formatting - name: Check translations - run: docker compose run --user=1000:1000 web make check-translations + run: docker compose run $(id -u):$(id -g) web make check-translations - name: Check migrations run: docker compose run web make check-migrations \ No newline at end of file From 8312b7ae9019cfd66f8c8bcb3bb8380032e5431a Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 15:11:49 +0100 Subject: [PATCH 25/59] fix $(id -u):$(id -g) --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 03b832551..f80c44f8f 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -12,6 +12,6 @@ jobs: - name: Check formatting run: docker compose run web make check-formatting - name: Check translations - run: docker compose run $(id -u):$(id -g) web make check-translations + run: docker compose run --user=$(id -u):$(id -g) web make check-translations - name: Check migrations run: docker compose run web make check-migrations \ No newline at end of file From 5faddcfcf18c7be852076429dc462dacb9b36c66 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 16:12:33 +0100 Subject: [PATCH 26/59] add $(id -u):$(id -g) to test --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 49fb4148a..f5c97a901 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Run Test - run: docker compose run web make test + run: docker compose run --user=$(id -u):$(id -g) web make test - name: Upload coverage uses: actions/upload-artifact@master with: From f771e9c208b560fbe5fadd255cec1ef75aba9fac Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 16:44:53 +0100 Subject: [PATCH 27/59] add $(id -u):$(id -g) to to documentation --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3a91f4c8..e49e7c8a0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -129,7 +129,7 @@ docker compose up -d openldap Then, run the tests. ```sh -docker compose run --rm web pytest +docker compose run --user=$(id -u):$(id -g) --rm web pytest ``` The `--rm` option will delete the temporary containers created to run the tests. Omit it if you want to keep the @@ -146,7 +146,7 @@ To generate the translation files, first use "makemessages" and specify the lang ```sh docker compose exec -w /app/tapir web python ../manage.py makemessages --no-wrap -l de -docker compose run --rm -w /app web python manage.py makemessages --no-wrap -l de -d djangojs +docker compose run --user=$(id -u):$(id -g) --rm -w /app web python manage.py makemessages --no-wrap -l de -d djangojs ``` Update tapir/translations/locale/de/LC_MESSAGES/django.po with your translations. From a9d7a20f834af12d3f94f133c0a4227563ad7cdc Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 17:39:15 +0100 Subject: [PATCH 28/59] Make sure no write permissions are assigned to the copied resource. --- django.Dockerfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/django.Dockerfile b/django.Dockerfile index f6b7bf898..1ee21d592 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -73,9 +73,11 @@ USER $USERNAME WORKDIR /app -COPY --from=build --chown=$USERNAME:$USERNAME /opt/pysetup/.venv /opt/pysetup/.venv -COPY --from=build --chown=$USERNAME:$USERNAME /opt/pysetup/ ./ -COPY --chown=$USERNAME:$USERNAME . . +# Make sure no write permissions are assigned to the copied resource. +# https://sonarcloud.io/organizations/supercoopberlin/rules?open=docker%3AS6504&rule_key=docker%3AS6504 +COPY --from=build --chown=root:root --chmod=755 /opt/pysetup/.venv /opt/pysetup/.venv +COPY --from=build --chown=root:root --chmod=755 /opt/pysetup/ ./ +COPY --chown=root:root --chmod=755 . . RUN python manage.py compilemessages From ab98ef863046deb025546d63d242315c679de60f Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 17:41:01 +0100 Subject: [PATCH 29/59] username nonroot --- django.Dockerfile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/django.Dockerfile b/django.Dockerfile index 1ee21d592..7d37fb329 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -53,7 +53,6 @@ RUN if [ "$DEV" = "true" ]; then \ FROM python:3.13-slim AS runtime ARG USER_UID=1000 ARG USER_GID=$USER_UID -ARG USERNAME=tapiruser ENV VENV_PATH="/opt/pysetup/.venv" \ PATH="/opt/pysetup/.venv/bin:$PATH" @@ -65,11 +64,11 @@ RUN apt-get update \ && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* -RUN groupadd --gid $USER_GID $USERNAME && \ - useradd --uid $USER_UID --gid $USER_GID -m $USERNAME && \ - mkdir -p /app \ +RUN groupadd --gid $USER_GID nonroot && \ + useradd --uid $USER_UID --gid $USER_GID -m nonroot && \ + mkdir -p /app -USER $USERNAME +USER nonroot WORKDIR /app From b4e167b6e692e89442841ea645277a9db1bd9d1d Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 17:52:50 +0100 Subject: [PATCH 30/59] Revert "Make sure no write permissions are assigned to the copied resource." This reverts commit a9d7a20f834af12d3f94f133c0a4227563ad7cdc. --- django.Dockerfile | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/django.Dockerfile b/django.Dockerfile index 7d37fb329..b30fb3221 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -72,11 +72,9 @@ USER nonroot WORKDIR /app -# Make sure no write permissions are assigned to the copied resource. -# https://sonarcloud.io/organizations/supercoopberlin/rules?open=docker%3AS6504&rule_key=docker%3AS6504 -COPY --from=build --chown=root:root --chmod=755 /opt/pysetup/.venv /opt/pysetup/.venv -COPY --from=build --chown=root:root --chmod=755 /opt/pysetup/ ./ -COPY --chown=root:root --chmod=755 . . +COPY --from=build --chown=$USERNAME:$USERNAME /opt/pysetup/.venv /opt/pysetup/.venv +COPY --from=build --chown=$USERNAME:$USERNAME /opt/pysetup/ ./ +COPY --chown=$USERNAME:$USERNAME . . RUN python manage.py compilemessages From 393cd1308cb8bb4ef7679736fba56f97f99428ab Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 17:55:57 +0100 Subject: [PATCH 31/59] username nonroot --- django.Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/django.Dockerfile b/django.Dockerfile index b30fb3221..c0c412dbd 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -53,6 +53,7 @@ RUN if [ "$DEV" = "true" ]; then \ FROM python:3.13-slim AS runtime ARG USER_UID=1000 ARG USER_GID=$USER_UID +ARG USERNAME=nonroot ENV VENV_PATH="/opt/pysetup/.venv" \ PATH="/opt/pysetup/.venv/bin:$PATH" @@ -68,7 +69,7 @@ RUN groupadd --gid $USER_GID nonroot && \ useradd --uid $USER_UID --gid $USER_GID -m nonroot && \ mkdir -p /app -USER nonroot +USER $USERNAME WORKDIR /app From 1e3e0ac2cfff4187eb625411182d00386889ab14 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:35:06 +0100 Subject: [PATCH 32/59] copy only specific files, loose general writing-permission and hence write members-current.csv to tmp --- django.Dockerfile | 9 +++++---- docker-compose.yml | 4 +++- .../management/commands/update_purchase_tracking_list.py | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/django.Dockerfile b/django.Dockerfile index c0c412dbd..8945cb9f0 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -69,14 +69,15 @@ RUN groupadd --gid $USER_GID nonroot && \ useradd --uid $USER_UID --gid $USER_GID -m nonroot && \ mkdir -p /app -USER $USERNAME - WORKDIR /app COPY --from=build --chown=$USERNAME:$USERNAME /opt/pysetup/.venv /opt/pysetup/.venv COPY --from=build --chown=$USERNAME:$USERNAME /opt/pysetup/ ./ -COPY --chown=$USERNAME:$USERNAME . . -RUN python manage.py compilemessages +COPY --chown=$USERNAME:$USERNAME tapir /app/tapir +COPY --chown=$USERNAME:$USERNAME manage.py /app/manage.py +COPY --chown=$USERNAME:$USERNAME Makefile /app/Makefile +RUN python manage.py compilemessages +USER $USERNAME diff --git a/docker-compose.yml b/docker-compose.yml index 1241b2b7e..c88577897 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,7 +22,9 @@ services: command: bash -c "python manage.py runserver_plus 0.0.0.0:80" user: "1000:1000" volumes: - - .:/app + - ./tapir:/app/tapir # ganzer tapir-Ordner + - ./manage.py:/app/manage.py # einzelne Datei + - ./Makefile:/app/Makefile # einzelne Datei environment: VIRTUAL_HOST: localhost DEBUG: 1 diff --git a/tapir/accounts/management/commands/update_purchase_tracking_list.py b/tapir/accounts/management/commands/update_purchase_tracking_list.py index 5070b9a71..10c3c0457 100644 --- a/tapir/accounts/management/commands/update_purchase_tracking_list.py +++ b/tapir/accounts/management/commands/update_purchase_tracking_list.py @@ -17,7 +17,7 @@ class Command(BaseCommand): "Updates the file containing the list of users that allowed purchase tracking and synchronizes it with the " "BioOffice server." ) - FILE_NAME = "members-current.csv" + FILE_NAME = "/tmp/members-current.csv" def handle(self, *args, **options): self.write_users_to_file() From 44fb9deb0f894d2aeae83c36b37cc4e60d7b26e4 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 22:30:40 +0100 Subject: [PATCH 33/59] create volume only here to get coverage.xml --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f5c97a901..27341252c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Run Test - run: docker compose run --user=$(id -u):$(id -g) web make test + run: docker compose run --user=$(id -u):$(id -g) -v .:/app --rm web make test - name: Upload coverage uses: actions/upload-artifact@master with: From 7526e134531d997cc10cafc37a3be5daf91f3476 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 1 Feb 2026 22:31:10 +0100 Subject: [PATCH 34/59] chown app before writing --- django.Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/django.Dockerfile b/django.Dockerfile index 8945cb9f0..3b6cc025a 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -70,6 +70,7 @@ RUN groupadd --gid $USER_GID nonroot && \ mkdir -p /app WORKDIR /app +RUN chown -R $USERNAME:$USERNAME /app COPY --from=build --chown=$USERNAME:$USERNAME /opt/pysetup/.venv /opt/pysetup/.venv COPY --from=build --chown=$USERNAME:$USERNAME /opt/pysetup/ ./ From facc760596295416f2ab930d0b44980774e46c6b Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:55:13 +0100 Subject: [PATCH 35/59] use env, if available --- django.Dockerfile | 8 ++++---- docker-compose.yml | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/django.Dockerfile b/django.Dockerfile index 3b6cc025a..1623eb008 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -51,8 +51,8 @@ RUN if [ "$DEV" = "true" ]; then \ FROM python:3.13-slim AS runtime -ARG USER_UID=1000 -ARG USER_GID=$USER_UID +ARG UID=1000 +ARG GID=1000 ARG USERNAME=nonroot ENV VENV_PATH="/opt/pysetup/.venv" \ @@ -65,8 +65,8 @@ RUN apt-get update \ && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* -RUN groupadd --gid $USER_GID nonroot && \ - useradd --uid $USER_UID --gid $USER_GID -m nonroot && \ +RUN groupadd --gid $GID nonroot && \ + useradd --uid $UID --gid $GID -m nonroot && \ mkdir -p /app WORKDIR /app diff --git a/docker-compose.yml b/docker-compose.yml index c88577897..af534406f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,8 +19,9 @@ services: dockerfile: ./django.Dockerfile args: DEV: true + UID: ${UID:-1000} + GID: ${GID:-1000} command: bash -c "python manage.py runserver_plus 0.0.0.0:80" - user: "1000:1000" volumes: - ./tapir:/app/tapir # ganzer tapir-Ordner - ./manage.py:/app/manage.py # einzelne Datei From 245c7d732a9ef445ae9da7a0b69775cc45f01483 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sat, 7 Feb 2026 20:01:08 +0100 Subject: [PATCH 36/59] poetry lock: only changed content hash --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 53075c969..98aff4c9d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4289,4 +4289,4 @@ test = ["pytest"] [metadata] lock-version = "2.1" python-versions = ">=3.13,<4.0" -content-hash = "9c0de35a9dbc38d809658118e73a847e2bf3c9f90822bd7413d6d8418cf99810" +content-hash = "133fd908bdefacbcad8594400dca4e30cf910b5aeaafd32db0976a72efff1e81" From bd48180e7098c3cc688f4f4ad3e520594a3cdae0 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 8 Feb 2026 14:48:07 +0100 Subject: [PATCH 37/59] remove auto-purge (same as removing lists it seems) --- django.Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/django.Dockerfile b/django.Dockerfile index 1623eb008..59fd98ec2 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -33,7 +33,6 @@ RUN apt-get update \ # LDAP dependencies libldap2-dev libsasl2-dev \ # cleaning up unused files - && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=$POETRY_HOME python3 - && \ @@ -62,7 +61,6 @@ RUN apt-get update \ && apt-get install --no-install-recommends -y libpq-dev gettext libldap2-dev libsasl2-dev \ libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0 \ make git \ - && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* RUN groupadd --gid $GID nonroot && \ From ee3c3793f5e360602f1a968408e89cdb41ee5a47 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 8 Feb 2026 14:58:12 +0100 Subject: [PATCH 38/59] give nonroot-user full permission of app-directory, otherwise thos jobs fail writing files --- django.Dockerfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/django.Dockerfile b/django.Dockerfile index 59fd98ec2..90ccb457f 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -70,12 +70,12 @@ RUN groupadd --gid $GID nonroot && \ WORKDIR /app RUN chown -R $USERNAME:$USERNAME /app -COPY --from=build --chown=$USERNAME:$USERNAME /opt/pysetup/.venv /opt/pysetup/.venv -COPY --from=build --chown=$USERNAME:$USERNAME /opt/pysetup/ ./ +COPY --from=build /opt/pysetup/.venv /opt/pysetup/.venv +COPY --from=build /opt/pysetup/ ./ -COPY --chown=$USERNAME:$USERNAME tapir /app/tapir -COPY --chown=$USERNAME:$USERNAME manage.py /app/manage.py -COPY --chown=$USERNAME:$USERNAME Makefile /app/Makefile +COPY tapir /app/tapir +COPY manage.py /app/manage.py +COPY Makefile /app/Makefile RUN python manage.py compilemessages From eee1c6f57f2723429899860389bda84790d8d502 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 8 Feb 2026 15:21:26 +0100 Subject: [PATCH 39/59] remove comment --- docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index af534406f..56d829eab 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,9 +23,9 @@ services: GID: ${GID:-1000} command: bash -c "python manage.py runserver_plus 0.0.0.0:80" volumes: - - ./tapir:/app/tapir # ganzer tapir-Ordner - - ./manage.py:/app/manage.py # einzelne Datei - - ./Makefile:/app/Makefile # einzelne Datei + - ./tapir:/app/tapir + - ./manage.py:/app/manage.py + - ./Makefile:/app/Makefile environment: VIRTUAL_HOST: localhost DEBUG: 1 From d509691efa7ef9738fdb261bf6f7909f1fdb36fb Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 8 Feb 2026 15:21:52 +0100 Subject: [PATCH 40/59] remove chown -R $USERNAME:$USERNAME /app --- django.Dockerfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/django.Dockerfile b/django.Dockerfile index 90ccb457f..272ee24ed 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -64,11 +64,9 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* RUN groupadd --gid $GID nonroot && \ - useradd --uid $UID --gid $GID -m nonroot && \ - mkdir -p /app + useradd --uid $UID --gid $GID -m nonroot WORKDIR /app -RUN chown -R $USERNAME:$USERNAME /app COPY --from=build /opt/pysetup/.venv /opt/pysetup/.venv COPY --from=build /opt/pysetup/ ./ From 075358b9f535b13c21d29d8c4328a1120847e031 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 8 Feb 2026 15:38:58 +0100 Subject: [PATCH 41/59] use tempfile-package --- .../commands/update_purchase_tracking_list.py | 16 +++++++++++----- .../tests/test_purchase_tracking_setting.py | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/tapir/accounts/management/commands/update_purchase_tracking_list.py b/tapir/accounts/management/commands/update_purchase_tracking_list.py index 10c3c0457..e0f1523a6 100644 --- a/tapir/accounts/management/commands/update_purchase_tracking_list.py +++ b/tapir/accounts/management/commands/update_purchase_tracking_list.py @@ -1,4 +1,5 @@ import csv +import tempfile from django.core.management import BaseCommand @@ -17,15 +18,20 @@ class Command(BaseCommand): "Updates the file containing the list of users that allowed purchase tracking and synchronizes it with the " "BioOffice server." ) - FILE_NAME = "/tmp/members-current.csv" def handle(self, *args, **options): - self.write_users_to_file() - send_file_to_storage_server(self.FILE_NAME, "u326634-sub4") + with tempfile.NamedTemporaryFile( + mode="w", + prefix="members-current", + suffix=".csv", + dir="/tmp", + ) as temp_file: + self.write_users_to_file(filename=temp_file.name) + send_file_to_storage_server(temp_file.name, "u326634-sub4") @classmethod - def write_users_to_file(cls): - with open(cls.FILE_NAME, "w", newline="") as csvfile: + def write_users_to_file(cls, filename): + with open(filename, "w", newline="") as csvfile: writer = csv.writer(csvfile, delimiter=";", quoting=csv.QUOTE_MINIMAL) writer.writerow( [ diff --git a/tapir/accounts/tests/test_purchase_tracking_setting.py b/tapir/accounts/tests/test_purchase_tracking_setting.py index 615808d7c..31a35214e 100644 --- a/tapir/accounts/tests/test_purchase_tracking_setting.py +++ b/tapir/accounts/tests/test_purchase_tracking_setting.py @@ -14,7 +14,7 @@ class TestPurchaseTrackingSetting(TapirFactoryTestBase): - EXPORT_FILE = Path(UpdatePurchaseTrackingListCommand.FILE_NAME) + EXPORT_FILE = "/tmp/members-current.csv" def test_normal_user_can_update_their_own_setting(self): tapir_user = TapirUserFactory(allows_purchase_tracking=False) From d097d31034b185f3ab39e9fb7aafa26d16758c18 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:00:42 +0100 Subject: [PATCH 42/59] mock tempfile --- .../tests/test_purchase_tracking_setting.py | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/tapir/accounts/tests/test_purchase_tracking_setting.py b/tapir/accounts/tests/test_purchase_tracking_setting.py index 31a35214e..30768e2fd 100644 --- a/tapir/accounts/tests/test_purchase_tracking_setting.py +++ b/tapir/accounts/tests/test_purchase_tracking_setting.py @@ -1,4 +1,5 @@ from pathlib import Path +from unittest.mock import patch from django.core.management import call_command from django.urls import reverse @@ -14,7 +15,7 @@ class TestPurchaseTrackingSetting(TapirFactoryTestBase): - EXPORT_FILE = "/tmp/members-current.csv" + EXPORT_FILE = Path("/tmp/members-current.csv") def test_normal_user_can_update_their_own_setting(self): tapir_user = TapirUserFactory(allows_purchase_tracking=False) @@ -63,28 +64,43 @@ def test_no_log_entry_created_if_setting_value_not_changed(self): self.assertEqual(0, UpdateTapirUserLogEntry.objects.count()) - def test_updatePurchaseTrackingList_userHasTrackingEnabled_userIsInExportFile(self): + @patch("tempfile.NamedTemporaryFile") + def test_updatePurchaseTrackingList_userHasTrackingEnabled_userIsInExportFile( + self, mock_temp_file + ): + mock_temp_file_instance = mock_temp_file.return_value.__enter__.return_value + mock_temp_file_instance.name = "/tmp/test_temp_file.csv" + tapir_user = TapirUserFactory(allows_purchase_tracking=True) call_command("update_purchase_tracking_list") - list_content = self.EXPORT_FILE.read_text() + list_content = Path(mock_temp_file_instance.name).read_text() self.assertIn(tapir_user.last_name, list_content) - self.EXPORT_FILE.unlink() + Path(mock_temp_file_instance.name).unlink() + @patch("tempfile.NamedTemporaryFile") def test_updatePurchaseTrackingList_userHasTrackingDisabled_userIsNotInExportFile( - self, + self, mock_temp_file ): + mock_temp_file_instance = mock_temp_file.return_value.__enter__.return_value + mock_temp_file_instance.name = "/tmp/test_temp_file.csv" + tapir_user = TapirUserFactory(allows_purchase_tracking=False) call_command("update_purchase_tracking_list") - list_content = self.EXPORT_FILE.read_text() + + list_content = Path(mock_temp_file_instance.name).read_text() self.assertNotIn(tapir_user.last_name, list_content) - self.EXPORT_FILE.unlink() + Path(mock_temp_file_instance.name).unlink() + @patch("tempfile.NamedTemporaryFile") def test_updatePurchaseTrackingList_userHasTrackingEnabledButNoShareOwner_userIsNotInExportFile( - self, + self, mock_temp_file ): + mock_temp_file_instance = mock_temp_file.return_value.__enter__.return_value + mock_temp_file_instance.name = "/tmp/test_temp_file.csv" + tapir_user = TapirUserFactory(allows_purchase_tracking=True, share_owner=None) call_command("update_purchase_tracking_list") - list_content = self.EXPORT_FILE.read_text() + list_content = Path(mock_temp_file_instance.name).read_text() self.assertNotIn(tapir_user.last_name, list_content) - self.EXPORT_FILE.unlink() + Path(mock_temp_file_instance.name).unlink() From 684744d1862b299cc6e47fc2888d849f56432786 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:00:58 +0100 Subject: [PATCH 43/59] add prexix - --- .../management/commands/update_purchase_tracking_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tapir/accounts/management/commands/update_purchase_tracking_list.py b/tapir/accounts/management/commands/update_purchase_tracking_list.py index e0f1523a6..3c4fe2f18 100644 --- a/tapir/accounts/management/commands/update_purchase_tracking_list.py +++ b/tapir/accounts/management/commands/update_purchase_tracking_list.py @@ -22,7 +22,7 @@ class Command(BaseCommand): def handle(self, *args, **options): with tempfile.NamedTemporaryFile( mode="w", - prefix="members-current", + prefix="members-current-", suffix=".csv", dir="/tmp", ) as temp_file: From d9171ed56fc9ed86c3f4c57e0488ed7e1bf6dc6f Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sat, 21 Feb 2026 11:28:45 +0100 Subject: [PATCH 44/59] omit -L when installing poetry --- django.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django.Dockerfile b/django.Dockerfile index 272ee24ed..368b8e3e9 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -35,7 +35,7 @@ RUN apt-get update \ # cleaning up unused files && rm -rf /var/lib/apt/lists/* -RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=$POETRY_HOME python3 - && \ +RUN curl -sS https://install.python-poetry.org | POETRY_HOME=$POETRY_HOME python3 - && \ ln -s $POETRY_HOME/bin/poetry /usr/local/bin/poetry WORKDIR $PYSETUP_PATH From ce7efdafdf59dd72ebf18d6f95c8f52ddb2880ce Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 22 Feb 2026 11:20:00 +0100 Subject: [PATCH 45/59] merge tests --- .../tests/test_purchase_tracking_setting.py | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/tapir/accounts/tests/test_purchase_tracking_setting.py b/tapir/accounts/tests/test_purchase_tracking_setting.py index 30768e2fd..3831bb66a 100644 --- a/tapir/accounts/tests/test_purchase_tracking_setting.py +++ b/tapir/accounts/tests/test_purchase_tracking_setting.py @@ -65,31 +65,19 @@ def test_no_log_entry_created_if_setting_value_not_changed(self): self.assertEqual(0, UpdateTapirUserLogEntry.objects.count()) @patch("tempfile.NamedTemporaryFile") - def test_updatePurchaseTrackingList_userHasTrackingEnabled_userIsInExportFile( + def test_updatePurchaseTrackingList_userswithTrackingEnabledOrDisabled_includedOrExcludedInExportFile( self, mock_temp_file ): mock_temp_file_instance = mock_temp_file.return_value.__enter__.return_value mock_temp_file_instance.name = "/tmp/test_temp_file.csv" - tapir_user = TapirUserFactory(allows_purchase_tracking=True) + tapir_user_enabled = TapirUserFactory(allows_purchase_tracking=True) + tapir_user_disabled = TapirUserFactory(allows_purchase_tracking=False) call_command("update_purchase_tracking_list") list_content = Path(mock_temp_file_instance.name).read_text() - self.assertIn(tapir_user.last_name, list_content) - Path(mock_temp_file_instance.name).unlink() - - @patch("tempfile.NamedTemporaryFile") - def test_updatePurchaseTrackingList_userHasTrackingDisabled_userIsNotInExportFile( - self, mock_temp_file - ): - mock_temp_file_instance = mock_temp_file.return_value.__enter__.return_value - mock_temp_file_instance.name = "/tmp/test_temp_file.csv" - - tapir_user = TapirUserFactory(allows_purchase_tracking=False) - call_command("update_purchase_tracking_list") - - list_content = Path(mock_temp_file_instance.name).read_text() - self.assertNotIn(tapir_user.last_name, list_content) + self.assertIn(tapir_user_enabled.last_name, list_content) + self.assertNotIn(tapir_user_disabled.last_name, list_content) Path(mock_temp_file_instance.name).unlink() @patch("tempfile.NamedTemporaryFile") From 7be99805497dcecbf04bd594cc09e839195ee269 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 22 Feb 2026 11:20:18 +0100 Subject: [PATCH 46/59] remove unlink --- tapir/accounts/tests/test_purchase_tracking_setting.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tapir/accounts/tests/test_purchase_tracking_setting.py b/tapir/accounts/tests/test_purchase_tracking_setting.py index 3831bb66a..02a741a9c 100644 --- a/tapir/accounts/tests/test_purchase_tracking_setting.py +++ b/tapir/accounts/tests/test_purchase_tracking_setting.py @@ -78,7 +78,6 @@ def test_updatePurchaseTrackingList_userswithTrackingEnabledOrDisabled_includedO list_content = Path(mock_temp_file_instance.name).read_text() self.assertIn(tapir_user_enabled.last_name, list_content) self.assertNotIn(tapir_user_disabled.last_name, list_content) - Path(mock_temp_file_instance.name).unlink() @patch("tempfile.NamedTemporaryFile") def test_updatePurchaseTrackingList_userHasTrackingEnabledButNoShareOwner_userIsNotInExportFile( @@ -91,4 +90,3 @@ def test_updatePurchaseTrackingList_userHasTrackingEnabledButNoShareOwner_userIs call_command("update_purchase_tracking_list") list_content = Path(mock_temp_file_instance.name).read_text() self.assertNotIn(tapir_user.last_name, list_content) - Path(mock_temp_file_instance.name).unlink() From e7fd7d543c4514ba6a71b3191d9ce52e450436e1 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 22 Feb 2026 11:20:27 +0100 Subject: [PATCH 47/59] remove tmp --- .../management/commands/update_purchase_tracking_list.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tapir/accounts/management/commands/update_purchase_tracking_list.py b/tapir/accounts/management/commands/update_purchase_tracking_list.py index 3c4fe2f18..7372938dd 100644 --- a/tapir/accounts/management/commands/update_purchase_tracking_list.py +++ b/tapir/accounts/management/commands/update_purchase_tracking_list.py @@ -24,7 +24,6 @@ def handle(self, *args, **options): mode="w", prefix="members-current-", suffix=".csv", - dir="/tmp", ) as temp_file: self.write_users_to_file(filename=temp_file.name) send_file_to_storage_server(temp_file.name, "u326634-sub4") From a88b14fe1c4519ba2469a3e1fe97cd738583c916 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 22 Feb 2026 11:20:58 +0100 Subject: [PATCH 48/59] make sure members-current is written to correct target file --- tapir/utils/shortcuts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tapir/utils/shortcuts.py b/tapir/utils/shortcuts.py index e03ede274..568755ca3 100644 --- a/tapir/utils/shortcuts.py +++ b/tapir/utils/shortcuts.py @@ -91,7 +91,7 @@ def send_file_to_storage_server(filename: str, username: str): setup_ssh_for_biooffice_storage() os.system( - f"scp -o 'NumberOfPasswordPrompts=0' -o 'UserKnownHostsFile=~/.ssh/biooffice_known_hosts' -i ~/.ssh/biooffice_id_rsa -P 23 {filename} {username}@u326634.your-storagebox.de:./" + f"scp -o 'NumberOfPasswordPrompts=0' -o 'UserKnownHostsFile=~/.ssh/biooffice_known_hosts' -i ~/.ssh/biooffice_id_rsa -P 23 {filename} {username}@u326634.your-storagebox.de:./members-current.csv" ) From 9f9f62b8c0ec18d4eb716aa53fad2771e31040a8 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 22 Feb 2026 11:23:03 +0100 Subject: [PATCH 49/59] remove unused EXPORT_FILE --- tapir/accounts/tests/test_purchase_tracking_setting.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tapir/accounts/tests/test_purchase_tracking_setting.py b/tapir/accounts/tests/test_purchase_tracking_setting.py index 02a741a9c..e50949e22 100644 --- a/tapir/accounts/tests/test_purchase_tracking_setting.py +++ b/tapir/accounts/tests/test_purchase_tracking_setting.py @@ -15,7 +15,6 @@ class TestPurchaseTrackingSetting(TapirFactoryTestBase): - EXPORT_FILE = Path("/tmp/members-current.csv") def test_normal_user_can_update_their_own_setting(self): tapir_user = TapirUserFactory(allows_purchase_tracking=False) From 0d214f7d3e4d0ab1ec3879845c7297e773f19dd3 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 22 Feb 2026 11:29:16 +0100 Subject: [PATCH 50/59] use tempfile.TemporaryFile --- .../management/commands/update_purchase_tracking_list.py | 4 +--- tapir/accounts/tests/test_purchase_tracking_setting.py | 7 ++----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/tapir/accounts/management/commands/update_purchase_tracking_list.py b/tapir/accounts/management/commands/update_purchase_tracking_list.py index 7372938dd..5f9d94164 100644 --- a/tapir/accounts/management/commands/update_purchase_tracking_list.py +++ b/tapir/accounts/management/commands/update_purchase_tracking_list.py @@ -20,10 +20,8 @@ class Command(BaseCommand): ) def handle(self, *args, **options): - with tempfile.NamedTemporaryFile( + with tempfile.TemporaryFile( mode="w", - prefix="members-current-", - suffix=".csv", ) as temp_file: self.write_users_to_file(filename=temp_file.name) send_file_to_storage_server(temp_file.name, "u326634-sub4") diff --git a/tapir/accounts/tests/test_purchase_tracking_setting.py b/tapir/accounts/tests/test_purchase_tracking_setting.py index e50949e22..ec96da547 100644 --- a/tapir/accounts/tests/test_purchase_tracking_setting.py +++ b/tapir/accounts/tests/test_purchase_tracking_setting.py @@ -4,9 +4,6 @@ from django.core.management import call_command from django.urls import reverse -from tapir.accounts.management.commands.update_purchase_tracking_list import ( - Command as UpdatePurchaseTrackingListCommand, -) from tapir.accounts.models import UpdateTapirUserLogEntry from tapir.accounts.tests.factories.factories import TapirUserFactory from tapir.utils.tests_utils import ( @@ -63,7 +60,7 @@ def test_no_log_entry_created_if_setting_value_not_changed(self): self.assertEqual(0, UpdateTapirUserLogEntry.objects.count()) - @patch("tempfile.NamedTemporaryFile") + @patch("tempfile.TemporaryFile") def test_updatePurchaseTrackingList_userswithTrackingEnabledOrDisabled_includedOrExcludedInExportFile( self, mock_temp_file ): @@ -78,7 +75,7 @@ def test_updatePurchaseTrackingList_userswithTrackingEnabledOrDisabled_includedO self.assertIn(tapir_user_enabled.last_name, list_content) self.assertNotIn(tapir_user_disabled.last_name, list_content) - @patch("tempfile.NamedTemporaryFile") + @patch("tempfile.TemporaryFile") def test_updatePurchaseTrackingList_userHasTrackingEnabledButNoShareOwner_userIsNotInExportFile( self, mock_temp_file ): From 3389b9fc56e3080899ccddf1a42fa270df236d4b Mon Sep 17 00:00:00 2001 From: Frederik <18083323+crosspolar@users.noreply.github.com> Date: Fri, 27 Feb 2026 09:42:51 +0100 Subject: [PATCH 51/59] Update django.Dockerfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Théophile MADET --- django.Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django.Dockerfile b/django.Dockerfile index 368b8e3e9..b14c94879 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -63,8 +63,8 @@ RUN apt-get update \ make git \ && rm -rf /var/lib/apt/lists/* -RUN groupadd --gid $GID nonroot && \ - useradd --uid $UID --gid $GID -m nonroot +RUN groupadd --gid $GID $USERNAME && \ + useradd --uid $UID --gid $GID -m $USERNAME WORKDIR /app From dd040937fbedeba5551ccd258ddcc0729978786f Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sat, 28 Mar 2026 12:34:44 +0100 Subject: [PATCH 52/59] poetry lock --- poetry.lock | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2189bd528..3dd6c26f9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -48,7 +48,7 @@ version = "3.0.1" description = "Annotate AST trees with source code positions" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] files = [ {file = "asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a"}, {file = "asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7"}, @@ -64,7 +64,7 @@ version = "25.4.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, @@ -747,7 +747,6 @@ 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\""} [[package]] name = "contourpy" @@ -1442,7 +1441,7 @@ version = "3.16.1" description = "Web APIs for Django, made easy." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main"] files = [ {file = "djangorestframework-3.16.1-py3-none-any.whl", hash = "sha256:33a59f47fb9c85ede792cbf88bde71893bcda0667bc573f784649521f1102cec"}, {file = "djangorestframework-3.16.1.tar.gz", hash = "sha256:166809528b1aced0a17dc66c24492af18049f2c9420dbd0be29422029cfc3ff7"}, @@ -1500,7 +1499,7 @@ version = "0.29.0" description = "Sane and flexible OpenAPI 3 schema generation for Django REST framework" optional = false python-versions = ">=3.7" -groups = ["dev"] +groups = ["main"] files = [ {file = "drf_spectacular-0.29.0-py3-none-any.whl", hash = "sha256:d1ee7c9535d89848affb4427347f7c4a22c5d22530b8842ef133d7b72e19b41a"}, {file = "drf_spectacular-0.29.0.tar.gz", hash = "sha256:0a069339ea390ce7f14a75e8b5af4a0860a46e833fd4af027411a3e94fc1a0cc"}, @@ -1539,7 +1538,7 @@ version = "2.2.1" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main"] files = [ {file = "executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017"}, {file = "executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4"}, @@ -1764,7 +1763,7 @@ version = "2.1.10" 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"] +groups = ["main"] files = [ {file = "icecream-2.1.10-py3-none-any.whl", hash = "sha256:6b0ae3e899de12954cd26d8611dcff86518ff19f40deef333427da2ccf4036b2"}, {file = "icecream-2.1.10.tar.gz", hash = "sha256:15900126ba7dbe1f83819583cbe5ff79a2943224600878d89307e4633b32e528"}, @@ -1812,7 +1811,7 @@ version = "0.5.1" description = "A port of Ruby on Rails inflector to Python" optional = false python-versions = ">=3.5" -groups = ["dev"] +groups = ["main"] files = [ {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"}, {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, @@ -1876,7 +1875,7 @@ version = "4.26.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.10" -groups = ["dev"] +groups = ["main"] files = [ {file = "jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce"}, {file = "jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326"}, @@ -1898,7 +1897,7 @@ version = "2025.9.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main"] files = [ {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, @@ -2930,7 +2929,7 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -3247,7 +3246,7 @@ version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -3350,7 +3349,7 @@ version = "0.37.0" description = "JSON Referencing + Python" optional = false python-versions = ">=3.10" -groups = ["dev"] +groups = ["main"] files = [ {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, @@ -3512,7 +3511,7 @@ version = "0.30.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.10" -groups = ["dev"] +groups = ["main"] files = [ {file = "rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288"}, {file = "rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00"}, @@ -3939,7 +3938,7 @@ version = "4.2.0" description = "Implementation of RFC 6570 URI Templates" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main"] files = [ {file = "uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686"}, {file = "uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e"}, @@ -4227,4 +4226,4 @@ test = ["pytest"] [metadata] lock-version = "2.1" python-versions = ">=3.13,<4.0" -content-hash = "5dfccddc8366d0e2b4bd921394eae630571fd3a471c3c2c763f74f2746cd468b" +content-hash = "e1e8f05fdff98644022422558c488e63655223ce2c0a76032416b99ea4d3c9db" From 0ca6877c8c4ca4de7aa0f26977084a5f5a5bceca Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sat, 28 Mar 2026 12:38:52 +0100 Subject: [PATCH 53/59] use TapirUserFactory.create() --- .../tests/test_purchase_tracking_setting.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tapir/accounts/tests/test_purchase_tracking_setting.py b/tapir/accounts/tests/test_purchase_tracking_setting.py index ec96da547..5cec68b7d 100644 --- a/tapir/accounts/tests/test_purchase_tracking_setting.py +++ b/tapir/accounts/tests/test_purchase_tracking_setting.py @@ -14,7 +14,7 @@ class TestPurchaseTrackingSetting(TapirFactoryTestBase): def test_normal_user_can_update_their_own_setting(self): - tapir_user = TapirUserFactory(allows_purchase_tracking=False) + tapir_user = TapirUserFactory.create(allows_purchase_tracking=False) self.login_as_user(tapir_user) response = self.client.get( @@ -35,7 +35,7 @@ def test_normal_user_can_update_their_own_setting(self): self.assertEqual(tapir_user, log_entry.actor) def test_other_user_cant_update_tracking_setting(self): - tapir_user = TapirUserFactory(allows_purchase_tracking=False) + tapir_user = TapirUserFactory.create(allows_purchase_tracking=False) self.login_as_member_office_user() response = self.client.get( @@ -49,7 +49,7 @@ def test_other_user_cant_update_tracking_setting(self): self.assertEqual(False, tapir_user.allows_purchase_tracking) def test_no_log_entry_created_if_setting_value_not_changed(self): - tapir_user = TapirUserFactory(allows_purchase_tracking=False) + tapir_user = TapirUserFactory.create(allows_purchase_tracking=False) self.login_as_user(tapir_user) self.client.get( @@ -67,8 +67,8 @@ def test_updatePurchaseTrackingList_userswithTrackingEnabledOrDisabled_includedO mock_temp_file_instance = mock_temp_file.return_value.__enter__.return_value mock_temp_file_instance.name = "/tmp/test_temp_file.csv" - tapir_user_enabled = TapirUserFactory(allows_purchase_tracking=True) - tapir_user_disabled = TapirUserFactory(allows_purchase_tracking=False) + tapir_user_enabled = TapirUserFactory.create(allows_purchase_tracking=True) + tapir_user_disabled = TapirUserFactory.create(allows_purchase_tracking=False) call_command("update_purchase_tracking_list") list_content = Path(mock_temp_file_instance.name).read_text() @@ -82,7 +82,9 @@ def test_updatePurchaseTrackingList_userHasTrackingEnabledButNoShareOwner_userIs mock_temp_file_instance = mock_temp_file.return_value.__enter__.return_value mock_temp_file_instance.name = "/tmp/test_temp_file.csv" - tapir_user = TapirUserFactory(allows_purchase_tracking=True, share_owner=None) + tapir_user = TapirUserFactory.create( + allows_purchase_tracking=True, share_owner=None + ) call_command("update_purchase_tracking_list") list_content = Path(mock_temp_file_instance.name).read_text() self.assertNotIn(tapir_user.last_name, list_content) From a26ee077701f83a5fc939aa28eddb29acf2aec58 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sat, 28 Mar 2026 12:39:51 +0100 Subject: [PATCH 54/59] fix: duplicated args in compose --- docker-compose.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4b17c42aa..3e4134e23 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,11 +16,10 @@ services: web: build: context: . - args: - ARG_VERSION: "" dockerfile: ./django.Dockerfile args: DEV: true + ARG_VERSION: "" UID: ${UID:-1000} GID: ${GID:-1000} command: bash -c "python manage.py runserver_plus 0.0.0.0:80" From 76a6deacd11cdf4f91e079ced4955604e24eb881 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 29 Mar 2026 09:40:05 +0200 Subject: [PATCH 55/59] remove --user=$(id -u):$(id -g) --- .github/workflows/checks.yml | 2 +- .github/workflows/tests.yml | 2 +- CONTRIBUTING.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 4e9ce59cd..f8a01492d 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -12,6 +12,6 @@ jobs: - name: Check formatting run: docker compose run web make check-formatting - name: Check translations - run: docker compose run --user=$(id -u):$(id -g) web make check-translations + run: docker compose run web make check-translations - name: Check migrations run: docker compose run web make check-migrations \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5aa44aa89..6311ecb00 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Run Test - run: docker compose run --user=$(id -u):$(id -g) -v .:/app --rm web make test + run: docker compose run -v .:/app --rm web make test - name: Upload coverage uses: actions/upload-artifact@master with: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e49e7c8a0..a3a91f4c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -129,7 +129,7 @@ docker compose up -d openldap Then, run the tests. ```sh -docker compose run --user=$(id -u):$(id -g) --rm web pytest +docker compose run --rm web pytest ``` The `--rm` option will delete the temporary containers created to run the tests. Omit it if you want to keep the @@ -146,7 +146,7 @@ To generate the translation files, first use "makemessages" and specify the lang ```sh docker compose exec -w /app/tapir web python ../manage.py makemessages --no-wrap -l de -docker compose run --user=$(id -u):$(id -g) --rm -w /app web python manage.py makemessages --no-wrap -l de -d djangojs +docker compose run --rm -w /app web python manage.py makemessages --no-wrap -l de -d djangojs ``` Update tapir/translations/locale/de/LC_MESSAGES/django.po with your translations. From 30cf98f057b1ef8b7cfc1a979a592deacde25473 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 29 Mar 2026 10:06:50 +0200 Subject: [PATCH 56/59] you need to export GID AND UID --- .github/workflows/tests.yml | 4 ++++ django.Dockerfile | 17 +++++++++-------- docker-compose.yml | 4 ++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6311ecb00..04449fd8b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,6 +9,10 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Export runner UID/GID + run: | + echo "USER_UID=$(id -u)" >> $GITHUB_ENV + echo "USER_GID=$(id -g)" >> $GITHUB_ENV - name: Run Test run: docker compose run -v .:/app --rm web make test - name: Upload coverage diff --git a/django.Dockerfile b/django.Dockerfile index 417b888cb..470e14dd2 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -1,10 +1,10 @@ # syntax=docker/dockerfile:1 +ARG USER_UID=1000 +ARG USER_GID=1000 + FROM python:3.13-slim AS build ARG DEV=false -ARG ARG_VERSION -ENV TAPIR_VERSION=${ARG_VERSION} - ENV POETRY_VERSION=2.2.1 \ PYTHONUNBUFFERED=1 \ # prevents python creating .pyc files @@ -53,10 +53,11 @@ RUN if [ "$DEV" = "true" ]; then \ FROM python:3.13-slim AS runtime -ARG UID=1000 -ARG GID=1000 ARG USERNAME=nonroot - +ARG ARG_VERSION +ARG USER_UID +ARG USER_GID +ENV TAPIR_VERSION=${ARG_VERSION} ENV VENV_PATH="/opt/pysetup/.venv" \ PATH="/opt/pysetup/.venv/bin:$PATH" @@ -66,8 +67,8 @@ RUN apt-get update \ make git \ && rm -rf /var/lib/apt/lists/* -RUN groupadd --gid $GID $USERNAME && \ - useradd --uid $UID --gid $GID -m $USERNAME +RUN groupadd --gid $USER_GID $USERNAME && \ + useradd --uid $USER_UID --gid $USER_GID -m $USERNAME WORKDIR /app diff --git a/docker-compose.yml b/docker-compose.yml index 3e4134e23..f40d2a044 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,8 +20,8 @@ services: args: DEV: true ARG_VERSION: "" - UID: ${UID:-1000} - GID: ${GID:-1000} + USER_UID: ${USER_UID} + USER_GID: ${USER_GID} command: bash -c "python manage.py runserver_plus 0.0.0.0:80" volumes: - ./tapir:/app/tapir From a23c0d6793c453f310a19590aac76771654d18f8 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 29 Mar 2026 10:08:49 +0200 Subject: [PATCH 57/59] you need to export GID AND UID to checks.yml too --- .github/workflows/checks.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index f8a01492d..e7f8aebd3 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -9,6 +9,10 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Export runner UID/GID + run: | + echo "USER_UID=$(id -u)" >> $GITHUB_ENV + echo "USER_GID=$(id -g)" >> $GITHUB_ENV - name: Check formatting run: docker compose run web make check-formatting - name: Check translations From a24d3e5558aafabbb6737e3d373f3742d25f2926 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 29 Mar 2026 17:40:56 +0200 Subject: [PATCH 58/59] set default in compose not in dockerfile --- .github/workflows/checks.yml | 4 ---- django.Dockerfile | 4 ++-- docker-compose.yml | 4 ++-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index e7f8aebd3..f8a01492d 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -9,10 +9,6 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Export runner UID/GID - run: | - echo "USER_UID=$(id -u)" >> $GITHUB_ENV - echo "USER_GID=$(id -g)" >> $GITHUB_ENV - name: Check formatting run: docker compose run web make check-formatting - name: Check translations diff --git a/django.Dockerfile b/django.Dockerfile index 470e14dd2..8854a6954 100644 --- a/django.Dockerfile +++ b/django.Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG USER_UID=1000 -ARG USER_GID=1000 +ARG USER_UID +ARG USER_GID FROM python:3.13-slim AS build diff --git a/docker-compose.yml b/docker-compose.yml index f40d2a044..4a1672d91 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,8 +20,8 @@ services: args: DEV: true ARG_VERSION: "" - USER_UID: ${USER_UID} - USER_GID: ${USER_GID} + USER_UID: ${USER_UID:-1000} + USER_GID: ${USER_GID:-1000} command: bash -c "python manage.py runserver_plus 0.0.0.0:80" volumes: - ./tapir:/app/tapir From 9426724c7fcbfe34f66c19067745efef745bb6d0 Mon Sep 17 00:00:00 2001 From: crosspolar <18083323+crosspolar@users.noreply.github.com> Date: Sun, 29 Mar 2026 17:45:28 +0200 Subject: [PATCH 59/59] set USER_UID and USER_GID --- .github/workflows/checks.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index f8a01492d..e7f8aebd3 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -9,6 +9,10 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Export runner UID/GID + run: | + echo "USER_UID=$(id -u)" >> $GITHUB_ENV + echo "USER_GID=$(id -g)" >> $GITHUB_ENV - name: Check formatting run: docker compose run web make check-formatting - name: Check translations