generated from dopt-python/py311
further prototyping, added first DB interactions
This commit is contained in:
parent
e4ebb1ee7f
commit
c5aadd502d
283
pdm.lock
generated
283
pdm.lock
generated
@ -5,7 +5,7 @@
|
||||
groups = ["default", "dev", "lint", "nb", "tests"]
|
||||
strategy = ["inherit_metadata"]
|
||||
lock_version = "4.5.0"
|
||||
content_hash = "sha256:8bbf5d5c10eaccb24b54658cd427a2e389ec46a845545ccab37e930ea0348598"
|
||||
content_hash = "sha256:eb4aaeb1bc7efec0c69be5e4cde53b2d92fdacfcc170db0204ef694828f9e99d"
|
||||
|
||||
[[metadata.targets]]
|
||||
requires_python = ">=3.11,<3.14"
|
||||
@ -962,6 +962,20 @@ files = [
|
||||
{file = "docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dopt-basics"
|
||||
version = "0.2.4"
|
||||
requires_python = ">=3.11"
|
||||
summary = "basic cross-project tools for Python-based d-opt projects"
|
||||
groups = ["default"]
|
||||
dependencies = [
|
||||
"tzdata>=2025.1",
|
||||
]
|
||||
files = [
|
||||
{file = "dopt_basics-0.2.4-py3-none-any.whl", hash = "sha256:b7d05b80dde1f856b352580aeac500fc7505e4513ed162791d8735cdc182ebc1"},
|
||||
{file = "dopt_basics-0.2.4.tar.gz", hash = "sha256:c21fbe183bec5eab4cfd1404e10baca670035801596960822d0019e6e885983f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "execnet"
|
||||
version = "2.1.2"
|
||||
@ -1145,6 +1159,47 @@ files = [
|
||||
{file = "frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "3.4.0"
|
||||
requires_python = ">=3.10"
|
||||
summary = "Lightweight in-process concurrent programming"
|
||||
groups = ["default"]
|
||||
marker = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""
|
||||
files = [
|
||||
{file = "greenlet-3.4.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:805bebb4945094acbab757d34d6e1098be6de8966009ab9ca54f06ff492def58"},
|
||||
{file = "greenlet-3.4.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:439fc2f12b9b512d9dfa681c5afe5f6b3232c708d13e6f02c845e0d9f4c2d8c6"},
|
||||
{file = "greenlet-3.4.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a70ed1cb0295bee1df57b63bf7f46b4e56a5c93709eea769c1fec1bb23a95875"},
|
||||
{file = "greenlet-3.4.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c5696c42e6bb5cfb7c6ff4453789081c66b9b91f061e5e9367fa15792644e76"},
|
||||
{file = "greenlet-3.4.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c660bce1940a1acae5f51f0a064f1bc785d07ea16efcb4bc708090afc4d69e83"},
|
||||
{file = "greenlet-3.4.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:89995ce5ddcd2896d89615116dd39b9703bfa0c07b583b85b89bf1b5d6eddf81"},
|
||||
{file = "greenlet-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee407d4d1ca9dc632265aee1c8732c4a2d60adff848057cdebfe5fe94eb2c8a2"},
|
||||
{file = "greenlet-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:956215d5e355fffa7c021d168728321fd4d31fd730ac609b1653b450f6a4bc71"},
|
||||
{file = "greenlet-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:5cb614ace7c27571270354e9c9f696554d073f8aa9319079dcba466bbdead711"},
|
||||
{file = "greenlet-3.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:04403ac74fe295a361f650818de93be11b5038a78f49ccfb64d3b1be8fbf1267"},
|
||||
{file = "greenlet-3.4.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:1a54a921561dd9518d31d2d3db4d7f80e589083063ab4d3e2e950756ef809e1a"},
|
||||
{file = "greenlet-3.4.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16dec271460a9a2b154e3b1c2fa1050ce6280878430320e85e08c166772e3f97"},
|
||||
{file = "greenlet-3.4.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90036ce224ed6fe75508c1907a77e4540176dcf0744473627785dd519c6f9996"},
|
||||
{file = "greenlet-3.4.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6f0def07ec9a71d72315cf26c061aceee53b306c36ed38c35caba952ea1b319d"},
|
||||
{file = "greenlet-3.4.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1c4f6b453006efb8310affb2d132832e9bbb4fc01ce6df6b70d810d38f1f6dc"},
|
||||
{file = "greenlet-3.4.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:0e1254cf0cbaa17b04320c3a78575f29f3c161ef38f59c977108f19ffddaf077"},
|
||||
{file = "greenlet-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b2d9a138ffa0e306d0e2b72976d2fb10b97e690d40ab36a472acaab0838e2de"},
|
||||
{file = "greenlet-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8424683caf46eb0eb6f626cb95e008e8cc30d0cb675bdfa48200925c79b38a08"},
|
||||
{file = "greenlet-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0a53fb071531d003b075c444014ff8f8b1a9898d36bb88abd9ac7b3524648a2"},
|
||||
{file = "greenlet-3.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:f38b81880ba28f232f1f675893a39cf7b6db25b31cc0a09bb50787ecf957e85e"},
|
||||
{file = "greenlet-3.4.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:43748988b097f9c6f09364f260741aa73c80747f63389824435c7a50bfdfd5c1"},
|
||||
{file = "greenlet-3.4.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5566e4e2cd7a880e8c27618e3eab20f3494452d12fd5129edef7b2f7aa9a36d1"},
|
||||
{file = "greenlet-3.4.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1054c5a3c78e2ab599d452f23f7adafef55062a783a8e241d24f3b633ba6ff82"},
|
||||
{file = "greenlet-3.4.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:98eedd1803353daf1cd9ef23eef23eda5a4d22f99b1f998d273a8b78b70dd47f"},
|
||||
{file = "greenlet-3.4.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f82cb6cddc27dd81c96b1506f4aa7def15070c3b2a67d4e46fd19016aacce6cf"},
|
||||
{file = "greenlet-3.4.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:b7857e2202aae67bc5725e0c1f6403c20a8ff46094ece015e7d474f5f7020b55"},
|
||||
{file = "greenlet-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:227a46251ecba4ff46ae742bc5ce95c91d5aceb4b02f885487aff269c127a729"},
|
||||
{file = "greenlet-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5b99e87be7eba788dd5b75ba1cde5639edffdec5f91fe0d734a249535ec3408c"},
|
||||
{file = "greenlet-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:849f8bc17acd6295fcb5de8e46d55cc0e52381c56eaf50a2afd258e97bc65940"},
|
||||
{file = "greenlet-3.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:9390ad88b652b1903814eaabd629ca184db15e0eeb6fe8a390bbf8b9106ae15a"},
|
||||
{file = "greenlet-3.4.0.tar.gz", hash = "sha256:f50a96b64dafd6169e595a5c56c9146ef80333e67d4476a65a9c55f400fc22ff"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
@ -2397,6 +2452,38 @@ files = [
|
||||
{file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polars"
|
||||
version = "1.40.1"
|
||||
requires_python = ">=3.10"
|
||||
summary = "Blazingly fast DataFrame library"
|
||||
groups = ["default"]
|
||||
dependencies = [
|
||||
"polars-runtime-32==1.40.1",
|
||||
]
|
||||
files = [
|
||||
{file = "polars-1.40.1-py3-none-any.whl", hash = "sha256:c0f861219d1319cdea45c4ce4d30355a47176b8f98dcedf95ea8269f131b8abd"},
|
||||
{file = "polars-1.40.1.tar.gz", hash = "sha256:ab2694134b137596b5a59bfd7b4c54ebbc9b59f9403127f18e32d363777552e8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polars-runtime-32"
|
||||
version = "1.40.1"
|
||||
requires_python = ">=3.10"
|
||||
summary = "Blazingly fast DataFrame library"
|
||||
groups = ["default"]
|
||||
files = [
|
||||
{file = "polars_runtime_32-1.40.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b748ef652270cc49e9e69f99a035e0eb4d5f856d42bcd6ac4d9d80a40142aa1e"},
|
||||
{file = "polars_runtime_32-1.40.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:d249b3743e05986060cec0a7aaa542d020df6c6b876e556023a310efd581f9be"},
|
||||
{file = "polars_runtime_32-1.40.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5987b30e7aa1059d069498496e8dda35afd592b0ac3d46ed87e3ff8df1ad652c"},
|
||||
{file = "polars_runtime_32-1.40.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d7f42a8b3f16fc66002cc0f6516f7dd7653396886ae0ed362ab95c0b3408b59"},
|
||||
{file = "polars_runtime_32-1.40.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e5f7becc237a7ec9d9a10878dc8e54b73bbf4e2d94a2991c37d7a0b38590d8f9"},
|
||||
{file = "polars_runtime_32-1.40.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:992d14cf191dde043d36fbdbc98a65e43fbc7e9a5024cecd45f838ac4988c1ee"},
|
||||
{file = "polars_runtime_32-1.40.1-cp310-abi3-win_amd64.whl", hash = "sha256:f78bb2abd00101cbb23cc0cb068f7e36e081057a15d2ec2dde3dda280709f030"},
|
||||
{file = "polars_runtime_32-1.40.1-cp310-abi3-win_arm64.whl", hash = "sha256:b5cbfaf6b085b420b4bfcbe24e8f665076d1cccfdb80c0484c02a023ce205537"},
|
||||
{file = "polars_runtime_32-1.40.1.tar.gz", hash = "sha256:37f3065615d1bf90d03b5326222df4c5c1f8a5d33e50470aa588e3465e6eb814"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prometheus-client"
|
||||
version = "0.25.0"
|
||||
@ -2597,24 +2684,24 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.13.0"
|
||||
version = "2.13.3"
|
||||
requires_python = ">=3.9"
|
||||
summary = "Data validation using Python type hints"
|
||||
groups = ["default", "dev"]
|
||||
dependencies = [
|
||||
"annotated-types>=0.6.0",
|
||||
"pydantic-core==2.46.0",
|
||||
"pydantic-core==2.46.3",
|
||||
"typing-extensions>=4.14.1",
|
||||
"typing-inspection>=0.4.2",
|
||||
]
|
||||
files = [
|
||||
{file = "pydantic-2.13.0-py3-none-any.whl", hash = "sha256:ab0078b90da5f3e2fd2e71e3d9b457ddcb35d0350854fbda93b451e28d56baaf"},
|
||||
{file = "pydantic-2.13.0.tar.gz", hash = "sha256:b89b575b6e670ebf6e7448c01b41b244f471edd276cd0b6fe02e7e7aca320070"},
|
||||
{file = "pydantic-2.13.3-py3-none-any.whl", hash = "sha256:6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927"},
|
||||
{file = "pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.46.0"
|
||||
version = "2.46.3"
|
||||
requires_python = ">=3.9"
|
||||
summary = "Core functionality for Pydantic validation and serialization"
|
||||
groups = ["default", "dev"]
|
||||
@ -2622,90 +2709,60 @@ dependencies = [
|
||||
"typing-extensions>=4.14.1",
|
||||
]
|
||||
files = [
|
||||
{file = "pydantic_core-2.46.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0027da787ae711f7fbd5a76cb0bb8df526acba6c10c1e44581de1b838db10b7b"},
|
||||
{file = "pydantic_core-2.46.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:63e288fc18d7eaeef5f16c73e65c4fd0ad95b25e7e21d8a5da144977b35eb997"},
|
||||
{file = "pydantic_core-2.46.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:080a3bdc6807089a1fe1fbc076519cea287f1a964725731d80b49d8ecffaa217"},
|
||||
{file = "pydantic_core-2.46.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c065f1c3e54c3e79d909927a8cb48ccbc17b68733552161eba3e0628c38e5d19"},
|
||||
{file = "pydantic_core-2.46.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e2db58ab46cfe602d4255381cce515585998c3b6699d5b1f909f519bc44a5aa"},
|
||||
{file = "pydantic_core-2.46.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c660974890ec1e4c65cff93f5670a5f451039f65463e9f9c03ad49746b49fc78"},
|
||||
{file = "pydantic_core-2.46.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3be91482a8db77377c902cca87697388a4fb68addeb3e943ac74f425201a099"},
|
||||
{file = "pydantic_core-2.46.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:1c72de82115233112d70d07f26a48cf6996eb86f7e143423ec1a182148455a9d"},
|
||||
{file = "pydantic_core-2.46.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7904e58768cd79304b992868d7710bfc85dc6c7ed6163f0f68dbc1dcd72dc231"},
|
||||
{file = "pydantic_core-2.46.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1af8d88718005f57bb4768f92f4ff16bf31a747d39dfc919b22211b84e72c053"},
|
||||
{file = "pydantic_core-2.46.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:a5b891301b02770a5852253f4b97f8bd192e5710067bc129e20d43db5403ede2"},
|
||||
{file = "pydantic_core-2.46.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:48b671fe59031fd9754c7384ac05b3ed47a0cccb7d4db0ec56121f0e6a541b90"},
|
||||
{file = "pydantic_core-2.46.0-cp311-cp311-win32.whl", hash = "sha256:0a52b7262b6cc67033823e9549a41bb77580ac299dc964baae4e9c182b2e335c"},
|
||||
{file = "pydantic_core-2.46.0-cp311-cp311-win_amd64.whl", hash = "sha256:4103fea1beeef6b3a9fed8515f27d4fa30c929a1973655adf8f454dc49ee0662"},
|
||||
{file = "pydantic_core-2.46.0-cp311-cp311-win_arm64.whl", hash = "sha256:3137cd88938adb8e567c5e938e486adc7e518ffc96b4ae1ec268e6a4275704d7"},
|
||||
{file = "pydantic_core-2.46.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:66ccedb02c934622612448489824955838a221b3a35875458970521ef17b2f9c"},
|
||||
{file = "pydantic_core-2.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a44f27f4d2788ef9876ec47a43739b118c5904d74f418f53398f6ced3bbcacf2"},
|
||||
{file = "pydantic_core-2.46.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26a1032bcce6ca4b4670eb3f7d8195bd0a8b8f255f1307823e217ca3cfa7c27"},
|
||||
{file = "pydantic_core-2.46.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b8d1412f725060527e56675904b17a2d421dddcf861eecf7c75b9dda47921a4"},
|
||||
{file = "pydantic_core-2.46.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc3d1569edd859cabaa476cabce9eecd05049a7966af7b4a33b541bfd4ca1104"},
|
||||
{file = "pydantic_core-2.46.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:38108976f2d8afaa8f5067fd1390a8c9f5cc580175407cda636e76bc76e88054"},
|
||||
{file = "pydantic_core-2.46.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5a06d8ed01dad5575056b5187e5959b336793c6047920a3441ee5b03533836"},
|
||||
{file = "pydantic_core-2.46.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:04017ace142da9ce27cafd423a480872571b5c7e80382aec22f7d715ca8eb870"},
|
||||
{file = "pydantic_core-2.46.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2629ad992ed1b1c012e6067f5ffafd3336fcb9b54569449fabb85621f1444ed3"},
|
||||
{file = "pydantic_core-2.46.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3068b1e7bd986aebc88f6859f8353e72072538dcf92a7fb9cf511a0f61c5e729"},
|
||||
{file = "pydantic_core-2.46.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:1e366916ff69ff700aa9326601634e688581bc24c5b6b4f8738d809ec7d72611"},
|
||||
{file = "pydantic_core-2.46.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:485a23e8f4618a1b8e23ac744180acde283fffe617f96923d25507d5cade62ec"},
|
||||
{file = "pydantic_core-2.46.0-cp312-cp312-win32.whl", hash = "sha256:520940e1b702fe3b33525d0351777f25e9924f1818ca7956447dabacf2d339fd"},
|
||||
{file = "pydantic_core-2.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:90d2048e0339fa365e5a66aefe760ddd3b3d0a45501e088bc5bc7f4ed9ff9571"},
|
||||
{file = "pydantic_core-2.46.0-cp312-cp312-win_arm64.whl", hash = "sha256:a70247649b7dffe36648e8f34be5ce8c5fa0a27ff07b071ea780c20a738c05ce"},
|
||||
{file = "pydantic_core-2.46.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a05900c37264c070c683c650cbca8f83d7cbb549719e645fcd81a24592eac788"},
|
||||
{file = "pydantic_core-2.46.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de8e482fd4f1e3f36c50c6aac46d044462615d8f12cfafc6bebeaa0909eea22"},
|
||||
{file = "pydantic_core-2.46.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c525ecf8a4cdf198327b65030a7d081867ad8e60acb01a7214fff95cf9832d47"},
|
||||
{file = "pydantic_core-2.46.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f14581aeb12e61542ce73b9bfef2bca5439d65d9ab3efe1a4d8e346b61838f9b"},
|
||||
{file = "pydantic_core-2.46.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c108067f2f7e190d0dbd81247d789ec41f9ea50ccd9265a3a46710796ac60530"},
|
||||
{file = "pydantic_core-2.46.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ac10967e9a7bb1b96697374513f9a1a90a59e2fb41566b5e00ee45392beac59"},
|
||||
{file = "pydantic_core-2.46.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7897078fe8a13b73623c0955dfb2b3d2c9acb7177aac25144758c9e5a5265aaa"},
|
||||
{file = "pydantic_core-2.46.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:e69ce405510a419a082a78faed65bb4249cfb51232293cc675645c12f7379bf7"},
|
||||
{file = "pydantic_core-2.46.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fd28d13eea0d8cf351dc1fe274b5070cc8e1cca2644381dee5f99de629e77cf3"},
|
||||
{file = "pydantic_core-2.46.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:ee1547a6b8243e73dd10f585555e5a263395e55ce6dea618a078570a1e889aef"},
|
||||
{file = "pydantic_core-2.46.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:c3dc68dcf62db22a18ddfc3ad4960038f72b75908edc48ae014d7ac8b391d57a"},
|
||||
{file = "pydantic_core-2.46.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:004a2081c881abfcc6854a4623da6a09090a0d7c1398a6ae7133ca1256cee70b"},
|
||||
{file = "pydantic_core-2.46.0-cp313-cp313-win32.whl", hash = "sha256:59d24ec8d5eaabad93097525a69d0f00f2667cb353eb6cda578b1cfff203ceef"},
|
||||
{file = "pydantic_core-2.46.0-cp313-cp313-win_amd64.whl", hash = "sha256:71186dad5ac325c64d68fe0e654e15fd79802e7cc42bc6f0ff822d5ad8b1ab25"},
|
||||
{file = "pydantic_core-2.46.0-cp313-cp313-win_arm64.whl", hash = "sha256:8e4503f3213f723842c9a3b53955c88a9cfbd0b288cbd1c1ae933aebeec4a1b4"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:4fc801c290342350ffc82d77872054a934b2e24163727263362170c1db5416ca"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0a36f2cc88170cc177930afcc633a8c15907ea68b59ac16bd180c2999d714940"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a3912e0c568a1f99d4d6d3e41def40179d61424c0ca1c8c87c4877d7f6fd7fb"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3534c3415ed1a19ab23096b628916a827f7858ec8db49ad5d7d1e44dc13c0d7b"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21067396fc285609323a4db2f63a87570044abe0acddfcca8b135fc7948e3db7"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2afd85b7be186e2fe7cdbb09a3d964bcc2042f65bbcc64ad800b3c7915032655"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67e2c2e171b78db8154da602de72ffdc473c6ee51de8a9d80c0f1cd4051abfc7"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c16ae1f3170267b1a37e16dba5c297bdf60c8b5657b147909ca8774ce7366644"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:133b69e1c1ba34d3702eed73f19f7f966928f9aa16663b55c2ebce0893cca42e"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:15ed8e5bde505133d96b41702f31f06829c46b05488211a5b1c7877e11de5eb5"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:8cfc29a1c66a7f0fcb36262e92f353dd0b9c4061d558fceb022e698a801cb8ae"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e1155708540f13845bf68d5ac511a55c76cfe2e057ed12b4bf3adac1581fc5c2"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314-win32.whl", hash = "sha256:de5635a48df6b2eef161d10ea1bc2626153197333662ba4cd700ee7ec1aba7f5"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:f07a5af60c5e7cf53dd1ff734228bd72d0dc9938e64a75b5bb308ca350d9681e"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314-win_arm64.whl", hash = "sha256:e7a77eca3c7d5108ff509db20aae6f80d47c7ed7516d8b96c387aacc42f3ce0f"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5e7cdd4398bee1aaeafe049ac366b0f887451d9ae418fd8785219c13fea2f928"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5c2c92d82808e27cef3f7ab3ed63d657d0c755e0dbe5b8a58342e37bdf09bd2e"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bab80af91cd7014b45d1089303b5f844a9d91d7da60eabf3d5f9694b32a6655"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e49ffdb714bc990f00b39d1ad1d683033875b5af15582f60c1f34ad3eeccfaa"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca877240e8dbdeef3a66f751dc41e5a74893767d510c22a22fc5c0199844f0ce"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87e6843f89ecd2f596d7294e33196c61343186255b9880c4f1b725fde8b0e20d"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e20bc5add1dd9bc3b9a3600d40632e679376569098345500799a6ad7c5d46c72"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:ee6ff79a5f0289d64a9d6696a3ce1f98f925b803dd538335a118231e26d6d827"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:52d35cfb58c26323101c7065508d7bb69bb56338cda9ea47a7b32be581af055d"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d14cc5a6f260fa78e124061eebc5769af6534fc837e9a62a47f09a2c341fa4ea"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:4f7ff859d663b6635f6307a10803d07f0d09487e16c3d36b1744af51dbf948b2"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:8ef749be6ed0d69dba31902aaa8255a9bb269ae50c93888c4df242d8bb7acd9e"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314t-win32.whl", hash = "sha256:d93ca72870133f86360e4bb0c78cd4e6ba2a0f9f3738a6486909ffc031463b32"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6ebb2668afd657e2127cb40f2ceb627dd78e74e9dfde14d9bf6cdd532a29ff59"},
|
||||
{file = "pydantic_core-2.46.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4864f5bbb7993845baf9209bae1669a8a76769296a018cb569ebda9dcb4241f5"},
|
||||
{file = "pydantic_core-2.46.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:be3e04979ba4d68183f247202c7f4f483f35df57690b3f875c06340a1579b47c"},
|
||||
{file = "pydantic_core-2.46.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b1eae8d7d9b8c2a90b34d3d9014804dca534f7f40180197062634499412ea14e"},
|
||||
{file = "pydantic_core-2.46.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a95a2773680dd4b6b999d4eccdd1b577fd71c31739fb4849f6ada47eabb9c56"},
|
||||
{file = "pydantic_core-2.46.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25988c3159bb097e06abfdf7b21b1fcaf90f187c74ca6c7bb842c1f72ce74fa8"},
|
||||
{file = "pydantic_core-2.46.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:747d89bd691854c719a3381ba46b6124ef916ae85364c79e11db9c84995d8d03"},
|
||||
{file = "pydantic_core-2.46.0-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:909a7327b83ca93b372f7d48df0ebc7a975a5191eb0b6e024f503f4902c24124"},
|
||||
{file = "pydantic_core-2.46.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:2f7e6a3752378a69fadf3f5ee8bc5fa082f623703eec0f4e854b12c548322de0"},
|
||||
{file = "pydantic_core-2.46.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:ef47ee0a3ac4c2bb25a083b3acafb171f65be4a0ac1e84edef79dd0016e25eaa"},
|
||||
{file = "pydantic_core-2.46.0.tar.gz", hash = "sha256:82d2498c96be47b47e903e1378d1d0f770097ec56ea953322f39936a7cf34977"},
|
||||
{file = "pydantic_core-2.46.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ab124d49d0459b2373ecf54118a45c28a1e6d4192a533fbc915e70f556feb8e5"},
|
||||
{file = "pydantic_core-2.46.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cca67d52a5c7a16aed2b3999e719c4bcf644074eac304a5d3d62dd70ae7d4b2c"},
|
||||
{file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c024e08c0ba23e6fd68c771a521e9d6a792f2ebb0fa734296b36394dc30390e"},
|
||||
{file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6645ce7eec4928e29a1e3b3d5c946621d105d3e79f0c9cddf07c2a9770949287"},
|
||||
{file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a712c7118e6c5ea96562f7b488435172abb94a3c53c22c9efc1412264a45cbbe"},
|
||||
{file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69a868ef3ff206343579021c40faf3b1edc64b1cc508ff243a28b0a514ccb050"},
|
||||
{file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc7e8c32db809aa0f6ea1d6869ebc8518a65d5150fdfad8bcae6a49ae32a22e2"},
|
||||
{file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:3481bd1341dc85779ee506bc8e1196a277ace359d89d28588a9468c3ecbe63fa"},
|
||||
{file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8690eba565c6d68ffd3a8655525cbdd5246510b44a637ee2c6c03a7ebfe64d3c"},
|
||||
{file = "pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4de88889d7e88d50d40ee5b39d5dac0bcaef9ba91f7e536ac064e6b2834ecccf"},
|
||||
{file = "pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:e480080975c1ef7f780b8f99ed72337e7cc5efea2e518a20a692e8e7b278eb8b"},
|
||||
{file = "pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de3a5c376f8cd94da9a1b8fd3dd1c16c7a7b216ed31dc8ce9fd7a22bf13b836e"},
|
||||
{file = "pydantic_core-2.46.3-cp311-cp311-win32.whl", hash = "sha256:fc331a5314ffddd5385b9ee9d0d2fee0b13c27e0e02dad71b1ae5d6561f51eeb"},
|
||||
{file = "pydantic_core-2.46.3-cp311-cp311-win_amd64.whl", hash = "sha256:b5b9c6cf08a8a5e502698f5e153056d12c34b8fb30317e0c5fd06f45162a6346"},
|
||||
{file = "pydantic_core-2.46.3-cp311-cp311-win_arm64.whl", hash = "sha256:5dfd51cf457482f04ec49491811a2b8fd5b843b64b11eecd2d7a1ee596ea78a6"},
|
||||
{file = "pydantic_core-2.46.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b11b59b3eee90a80a36701ddb4576d9ae31f93f05cb9e277ceaa09e6bf074a67"},
|
||||
{file = "pydantic_core-2.46.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af8653713055ea18a3abc1537fe2ebc42f5b0bbb768d1eb79fd74eb47c0ac089"},
|
||||
{file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75a519dab6d63c514f3a81053e5266c549679e4aa88f6ec57f2b7b854aceb1b0"},
|
||||
{file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6cd87cb1575b1ad05ba98894c5b5c96411ef678fa2f6ed2576607095b8d9789"},
|
||||
{file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f80a55484b8d843c8ada81ebf70a682f3f00a3d40e378c06cf17ecb44d280d7d"},
|
||||
{file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3861f1731b90c50a3266316b9044f5c9b405eecb8e299b0a7120596334e4fe9c"},
|
||||
{file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb528e295ed31570ac3dcc9bfdd6e0150bc11ce6168ac87a8082055cf1a67395"},
|
||||
{file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:367508faa4973b992b271ba1494acaab36eb7e8739d1e47be5035fb1ea225396"},
|
||||
{file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ad3c826fe523e4becf4fe39baa44286cff85ef137c729a2c5e269afbfd0905d"},
|
||||
{file = "pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ec638c5d194ef8af27db69f16c954a09797c0dc25015ad6123eb2c73a4d271ca"},
|
||||
{file = "pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:28ed528c45446062ee66edb1d33df5d88828ae167de76e773a3c7f64bd14e976"},
|
||||
{file = "pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aed19d0c783886d5bd86d80ae5030006b45e28464218747dcf83dabfdd092c7b"},
|
||||
{file = "pydantic_core-2.46.3-cp312-cp312-win32.whl", hash = "sha256:06d5d8820cbbdb4147578c1fe7ffcd5b83f34508cb9f9ab76e807be7db6ff0a4"},
|
||||
{file = "pydantic_core-2.46.3-cp312-cp312-win_amd64.whl", hash = "sha256:c3212fda0ee959c1dd04c60b601ec31097aaa893573a3a1abd0a47bcac2968c1"},
|
||||
{file = "pydantic_core-2.46.3-cp312-cp312-win_arm64.whl", hash = "sha256:f1f8338dd7a7f31761f1f1a3c47503a9a3b34eea3c8b01fa6ee96408affb5e72"},
|
||||
{file = "pydantic_core-2.46.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:12bc98de041458b80c86c56b24df1d23832f3e166cbaff011f25d187f5c62c37"},
|
||||
{file = "pydantic_core-2.46.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85348b8f89d2c3508b65b16c3c33a4da22b8215138d8b996912bb1532868885f"},
|
||||
{file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1105677a6df914b1fb71a81b96c8cce7726857e1717d86001f29be06a25ee6f8"},
|
||||
{file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87082cd65669a33adeba5470769e9704c7cf026cc30afb9cc77fd865578ebaad"},
|
||||
{file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e5f66e12c4f5212d08522963380eaaeac5ebd795826cfd19b2dfb0c7a52b9c"},
|
||||
{file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6cdf19bf84128d5e7c37e8a73a0c5c10d51103a650ac585d42dd6ae233f2b7f"},
|
||||
{file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031bb17f4885a43773c8c763089499f242aee2ea85cf17154168775dccdecf35"},
|
||||
{file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:bcf2a8b2982a6673693eae7348ef3d8cf3979c1d63b54fca7c397a635cc68687"},
|
||||
{file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28e8cf2f52d72ced402a137145923a762cbb5081e48b34312f7a0c8f55928ec3"},
|
||||
{file = "pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:17eaface65d9fc5abb940003020309c1bf7a211f5f608d7870297c367e6f9022"},
|
||||
{file = "pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:93fd339f23408a07e98950a89644f92c54d8729719a40b30c0a30bb9ebc55d23"},
|
||||
{file = "pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:23cbdb3aaa74dfe0837975dbf69b469753bbde8eacace524519ffdb6b6e89eb7"},
|
||||
{file = "pydantic_core-2.46.3-cp313-cp313-win32.whl", hash = "sha256:610eda2e3838f401105e6326ca304f5da1e15393ae25dacae5c5c63f2c275b13"},
|
||||
{file = "pydantic_core-2.46.3-cp313-cp313-win_amd64.whl", hash = "sha256:68cc7866ed863db34351294187f9b729964c371ba33e31c26f478471c52e1ed0"},
|
||||
{file = "pydantic_core-2.46.3-cp313-cp313-win_arm64.whl", hash = "sha256:f64b5537ac62b231572879cd08ec05600308636a5d63bcbdb15063a466977bec"},
|
||||
{file = "pydantic_core-2.46.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:13afdd885f3d71280cf286b13b310ee0f7ccfefd1dbbb661514a474b726e2f25"},
|
||||
{file = "pydantic_core-2.46.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f91c0aff3e3ee0928edd1232c57f643a7a003e6edf1860bc3afcdc749cb513f3"},
|
||||
{file = "pydantic_core-2.46.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6529d1d128321a58d30afcc97b49e98836542f68dd41b33c2e972bb9e5290536"},
|
||||
{file = "pydantic_core-2.46.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:975c267cff4f7e7272eacbe50f6cc03ca9a3da4c4fbd66fffd89c94c1e311aa1"},
|
||||
{file = "pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2b8e4f2bbdf71415c544b4b1138b8060db7b6611bc927e8064c769f64bed651c"},
|
||||
{file = "pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e61ea8e9fff9606d09178f577ff8ccdd7206ff73d6552bcec18e1033c4254b85"},
|
||||
{file = "pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b504bda01bafc69b6d3c7a0c7f039dcf60f47fab70e06fe23f57b5c75bdc82b8"},
|
||||
{file = "pydantic_core-2.46.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b00b76f7142fc60c762ce579bd29c8fa44aaa56592dd3c54fab3928d0d4ca6ff"},
|
||||
{file = "pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3585,6 +3642,49 @@ files = [
|
||||
{file = "soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "2.0.49"
|
||||
requires_python = ">=3.7"
|
||||
summary = "Database Abstraction Library"
|
||||
groups = ["default"]
|
||||
dependencies = [
|
||||
"greenlet>=1; platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\"",
|
||||
"importlib-metadata; python_version < \"3.8\"",
|
||||
"typing-extensions>=4.6.0",
|
||||
]
|
||||
files = [
|
||||
{file = "sqlalchemy-2.0.49-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5070135e1b7409c4161133aa525419b0062088ed77c92b1da95366ec5cbebbe"},
|
||||
{file = "sqlalchemy-2.0.49-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ac7a3e245fd0310fd31495eb61af772e637bdf7d88ee81e7f10a3f271bff014"},
|
||||
{file = "sqlalchemy-2.0.49-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d4e5a0ceba319942fa6b585cf82539288a61e314ef006c1209f734551ab9536"},
|
||||
{file = "sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ddcb27fb39171de36e207600116ac9dfd4ae46f86c82a9bf3934043e80ebb88"},
|
||||
{file = "sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:32fe6a41ad97302db2931f05bb91abbcc65b5ce4c675cd44b972428dd2947700"},
|
||||
{file = "sqlalchemy-2.0.49-cp311-cp311-win32.whl", hash = "sha256:46d51518d53edfbe0563662c96954dc8fcace9832332b914375f45a99b77cc9a"},
|
||||
{file = "sqlalchemy-2.0.49-cp311-cp311-win_amd64.whl", hash = "sha256:951d4a210744813be63019f3df343bf233b7432aadf0db54c75802247330d3af"},
|
||||
{file = "sqlalchemy-2.0.49-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbccb45260e4ff1b7db0be80a9025bb1e6698bdb808b83fff0000f7a90b2c0b"},
|
||||
{file = "sqlalchemy-2.0.49-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb37f15714ec2652d574f021d479e78cd4eb9d04396dca36568fdfffb3487982"},
|
||||
{file = "sqlalchemy-2.0.49-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb9ec6436a820a4c006aad1ac351f12de2f2dbdaad171692ee457a02429b672"},
|
||||
{file = "sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8d6efc136f44a7e8bc8088507eaabbb8c2b55b3dbb63fe102c690da0ddebe55e"},
|
||||
{file = "sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e06e617e3d4fd9e51d385dfe45b077a41e9d1b033a7702551e3278ac597dc750"},
|
||||
{file = "sqlalchemy-2.0.49-cp312-cp312-win32.whl", hash = "sha256:83101a6930332b87653886c01d1ee7e294b1fe46a07dd9a2d2b4f91bcc88eec0"},
|
||||
{file = "sqlalchemy-2.0.49-cp312-cp312-win_amd64.whl", hash = "sha256:618a308215b6cececb6240b9abde545e3acdabac7ae3e1d4e666896bf5ba44b4"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df2d441bacf97022e81ad047e1597552eb3f83ca8a8f1a1fdd43cd7fe3898120"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e20e511dc15265fb433571391ba313e10dd8ea7e509d51686a51313b4ac01a2"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47604cb2159f8bbd5a1ab48a714557156320f20871ee64d550d8bf2683d980d3"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:22d8798819f86720bc646ab015baff5ea4c971d68121cb36e2ebc2ee43ead2b7"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b1c058c171b739e7c330760044803099c7fff11511e3ab3573e5327116a9c33"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313-win32.whl", hash = "sha256:a143af2ea6672f2af3f44ed8f9cd020e9cc34c56f0e8db12019d5d9ecf41cb3b"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl", hash = "sha256:12b04d1db2663b421fe072d638a138460a51d5a862403295671c4f3987fb9148"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24bd94bb301ec672d8f0623eba9226cc90d775d25a0c92b5f8e4965d7f3a1518"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a51d3db74ba489266ef55c7a4534eb0b8db9a326553df481c11e5d7660c8364d"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:55250fe61d6ebfd6934a272ee16ef1244e0f16b7af6cd18ab5b1fc9f08631db0"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:46796877b47034b559a593d7e4b549aba151dae73f9e78212a3478161c12ab08"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313t-win32.whl", hash = "sha256:9c4969a86e41454f2858256c39bdfb966a20961e9b58bf8749b65abf447e9a8d"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313t-win_amd64.whl", hash = "sha256:b9870d15ef00e4d0559ae10ee5bc71b654d1f20076dbe8bc7ed19b4c0625ceba"},
|
||||
{file = "sqlalchemy-2.0.49-py3-none-any.whl", hash = "sha256:ec44cfa7ef1a728e88ad41674de50f6db8cfdb3e2af84af86e0041aaf02d43d0"},
|
||||
{file = "sqlalchemy-2.0.49.tar.gz", hash = "sha256:d15950a57a210e36dd4cec1aac22787e2a4d57ba9318233e2ef8b2daf9ff2d5f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stack-data"
|
||||
version = "0.6.3"
|
||||
@ -3716,8 +3816,7 @@ name = "tzdata"
|
||||
version = "2026.1"
|
||||
requires_python = ">=2"
|
||||
summary = "Provider of IANA time zone data"
|
||||
groups = ["nb"]
|
||||
marker = "python_version >= \"3.9\""
|
||||
groups = ["default", "nb"]
|
||||
files = [
|
||||
{file = "tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9"},
|
||||
{file = "tzdata-2026.1.tar.gz", hash = "sha256:67658a1903c75917309e753fdc349ac0efd8c27db7a0cb406a25be4840f87f98"},
|
||||
|
||||
142
prototypes/db_access.py
Normal file
142
prototypes/db_access.py
Normal file
@ -0,0 +1,142 @@
|
||||
# %%
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
|
||||
import polars as pl
|
||||
import sqlalchemy as sql
|
||||
|
||||
from wce_crm import db
|
||||
|
||||
importlib.reload(db)
|
||||
|
||||
# %%
|
||||
PTH_DATA_DB = Path.cwd().parent / "data/db"
|
||||
assert PTH_DATA_DB.exists()
|
||||
assert PTH_DATA_DB.is_dir()
|
||||
# %%
|
||||
DB_KL = PTH_DATA_DB / "wce_kontaktliste.db"
|
||||
DB_CRM = PTH_DATA_DB / "wce_crm.db"
|
||||
assert DB_KL.exists()
|
||||
assert DB_CRM.exists()
|
||||
# %%
|
||||
engine = sql.create_engine(f"sqlite:///{DB_CRM}")
|
||||
# %%
|
||||
db.df_crm_master
|
||||
# %%
|
||||
stmt = sql.select(db.ext_crm_master)
|
||||
str(stmt.compile(engine))
|
||||
df = pl.read_database(stmt, engine, schema_overrides=db.ext_crm_master_schema)
|
||||
# df = pl.concat([df, df[:2]])
|
||||
# %%
|
||||
df.select("ma_unternehmensname").is_duplicated().sum()
|
||||
# %%
|
||||
q = df.lazy()
|
||||
counter = pl.int_range(0, pl.len()).over(pl.col.ma_unternehmensname)
|
||||
q = q.with_columns(
|
||||
ma_unternehmensname_dedupl=pl.when(counter == 0)
|
||||
.then(pl.col.ma_unternehmensname)
|
||||
.otherwise(pl.format("{} ({})", pl.col.ma_unternehmensname, counter))
|
||||
)
|
||||
df = q.collect()
|
||||
df.select("ma_unternehmensname_dedupl").is_duplicated().sum()
|
||||
|
||||
# %%
|
||||
# mapping dedupl text to idx
|
||||
df.head()
|
||||
|
||||
# dict(zip(df["ma_unternehmensname_dedupl"], df["ma_id"]))
|
||||
|
||||
# %%
|
||||
sub = df[0]
|
||||
sub
|
||||
# sub.with_columns(
|
||||
# # pl.when(pl.col(pl.Boolean)).then(pl.lit("Ja")).otherwise(pl.lit("Nein"))
|
||||
# pl.when(pl.col(pl.Boolean)).then(pl.lit("Ja")).otherwise(pl.lit("Nein")).name.keep()
|
||||
# )
|
||||
# %%
|
||||
q = (
|
||||
sub.lazy()
|
||||
.with_columns(
|
||||
pl.col(pl.Datetime).dt.to_string("%d.%m.%Y"),
|
||||
pl.col(pl.Date).dt.to_string("%d.%m.%Y"),
|
||||
pl.when(pl.col(pl.Boolean)).then(pl.lit("Ja")).otherwise(pl.lit("Nein")).name.keep(),
|
||||
)
|
||||
.with_columns(pl.all().cast(pl.String))
|
||||
)
|
||||
sub = q.collect()
|
||||
sub
|
||||
# %%
|
||||
df.row(0, named=True)
|
||||
|
||||
# %%
|
||||
db.df_crm_master.estimated_size("mb")
|
||||
|
||||
|
||||
# %%
|
||||
# // CRM Nutzer
|
||||
stmt = sql.select(db.ext_crm_nutzer).limit(20)
|
||||
str(stmt.compile(engine))
|
||||
df = pl.read_database(stmt, engine, schema_overrides=db.ext_crm_nutzer_schema)
|
||||
# %%
|
||||
stmt = sql.text("""SELECT ma_unternehmensname, ma_ersteintrag_datum, ma_aktualisierung_datum
|
||||
FROM Master
|
||||
WHERE ma_ersteintrag_datum LIKE '%ff'
|
||||
LIMIT 10;""")
|
||||
|
||||
with engine.connect() as con:
|
||||
res = con.execute(stmt)
|
||||
|
||||
print(res.fetchall())
|
||||
|
||||
# %%
|
||||
# ----------------------------------------------------------------
|
||||
engine = sql.create_engine(f"sqlite:///{DB_KL}")
|
||||
stmt = sql.select(db.ext_kl_unternehmen.c.u_firmenname).limit(20)
|
||||
|
||||
with engine.connect() as con:
|
||||
res = con.execute(stmt)
|
||||
|
||||
res.scalars().all()
|
||||
# %%
|
||||
for _ in res.mappings():
|
||||
print(_)
|
||||
# %%
|
||||
# %%
|
||||
stmt = sql.select(db.ext_kl_unternehmen)
|
||||
df = pl.read_database(stmt, engine, schema_overrides=db.ext_kl_unternehmen_schema)
|
||||
# %%
|
||||
df
|
||||
# %%
|
||||
df.estimated_size("mb")
|
||||
# %%
|
||||
df.height
|
||||
# %%
|
||||
db.df_kontaktliste
|
||||
# %%
|
||||
sub = db.df_kontaktliste.select(["u_id", "u_firmenname"]).lazy()
|
||||
# %%
|
||||
counter = pl.int_range(0, pl.len()).over(pl.col.u_firmenname)
|
||||
sub = sub.with_columns(
|
||||
t=pl.when(counter == 0)
|
||||
.then(pl.col.u_firmenname)
|
||||
.otherwise(pl.format("{} ({})", pl.col.u_firmenname, counter))
|
||||
)
|
||||
# %%
|
||||
sub.collect()
|
||||
|
||||
# %%
|
||||
# 1. Create a sample DataFrame
|
||||
df = pl.DataFrame({"text_col": ["TEST", "APPLE", "TEST", "TEST", "BANANA", "APPLE"]})
|
||||
|
||||
# 2. Define the window function to count occurrences
|
||||
# This generates a sequence [0, 1, 2...] for each unique string
|
||||
counter = pl.int_range(0, pl.len()).over("text_col")
|
||||
|
||||
# 3. Apply the conditional formatting
|
||||
df = df.with_columns(
|
||||
updated_col=pl.when(counter == 0)
|
||||
.then(pl.col("text_col")) # Keep original for the first occurrence
|
||||
.otherwise(pl.format("{} ({})", pl.col("text_col"), counter)) # Format duplicates
|
||||
)
|
||||
# %%
|
||||
df
|
||||
@ -1,24 +1,30 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses as dc
|
||||
import enum
|
||||
import sys
|
||||
import time
|
||||
from collections.abc import Sequence
|
||||
|
||||
from PySide6.QtCore import Qt, Signal # Signal ist wichtig!
|
||||
from PySide6.QtCore import QDate, Qt, QTimer, Signal # Signal ist wichtig!
|
||||
from PySide6.QtGui import QAction
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QComboBox,
|
||||
QCompleter,
|
||||
QDateEdit,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QFormLayout,
|
||||
QFrame,
|
||||
QGridLayout,
|
||||
QGroupBox,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QListWidget,
|
||||
QMainWindow,
|
||||
QMessageBox,
|
||||
QPlainTextEdit,
|
||||
QPushButton,
|
||||
QScrollArea,
|
||||
@ -28,6 +34,22 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from wce_crm.backend import initial_recording as be_init_rec
|
||||
|
||||
QSS = """
|
||||
*[styleClass="stempel"] {
|
||||
background-color: #f1f5f9;
|
||||
color: #333D4B;
|
||||
border: 1px dashed #cbd5e1;
|
||||
border-radius: 4px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
*[styleClass="stempel"]:focus {
|
||||
border: 1px dashed #cbd5e1;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
@dc.dataclass(slots=True)
|
||||
class Address:
|
||||
@ -134,16 +156,24 @@ class AddressForm_Search(QWidget):
|
||||
super().__init__()
|
||||
|
||||
main_layout = QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
form_layout = QFormLayout()
|
||||
form_layout.setSpacing(15)
|
||||
form_layout.setSpacing(10)
|
||||
|
||||
# title
|
||||
title = QLabel("--- Suche Unternehmen ---")
|
||||
title.setStyleSheet("font-size: 14px; font-style: italic;") # font-weight: bold;
|
||||
main_layout.addWidget(title)
|
||||
|
||||
self.search_input = QLineEdit(placeholderText="Tippen zum Suchen...")
|
||||
form_layout.addRow("Suche:", self.search_input)
|
||||
search_data = [addr.name for addr in ADDRESSES]
|
||||
self.SEARCH_MAP = {addr.name: addr for addr in ADDRESSES}
|
||||
self.completer = QCompleter(search_data)
|
||||
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||||
self.completer.setFilterMode(Qt.MatchContains)
|
||||
# search_data = [addr.name for addr in ADDRESSES]
|
||||
# self.SEARCH_MAP = {addr.name: addr for addr in ADDRESSES}
|
||||
self.SEARCH_MAP = be_init_rec.comp_search_choice_mapping()
|
||||
self.search_data = tuple(self.SEARCH_MAP.keys())
|
||||
self.completer = QCompleter(self.search_data)
|
||||
self.completer.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
|
||||
self.completer.setFilterMode(Qt.MatchFlag.MatchContains)
|
||||
self.search_input.setCompleter(self.completer)
|
||||
self.completer.activated.connect(self.search_result_selected)
|
||||
|
||||
@ -207,15 +237,23 @@ class AddressForm_Search(QWidget):
|
||||
}
|
||||
""")
|
||||
|
||||
def fill_out(self, address: Address):
|
||||
addr_ = address.export()
|
||||
def fill_out(self, comp_info: be_init_rec.CompanyInfo):
|
||||
# addr_ = address.export()
|
||||
|
||||
for field, value in zip(self.autofilled_fields, addr_.values()):
|
||||
field.setText(value)
|
||||
# for field, value in zip(self.autofilled_fields, addr_.values()):
|
||||
# field.setText(value)
|
||||
self.company_input.setText(comp_info["ma_unternehmensname"])
|
||||
self.street_input.setText(comp_info["ma_strasse"])
|
||||
self.number_input.setText(comp_info["ma_hausnummer"])
|
||||
self.zip_input.setText(comp_info["ma_plz"])
|
||||
self.city_input.setText(comp_info["ma_ort"])
|
||||
|
||||
def search_result_selected(self, name):
|
||||
address = self.SEARCH_MAP[name]
|
||||
self.fill_out(address)
|
||||
|
||||
comp_info = be_init_rec.comp_search_get_info(
|
||||
ma_id=self.SEARCH_MAP[name],
|
||||
)
|
||||
self.fill_out(comp_info)
|
||||
|
||||
|
||||
class DropdownSearch(QWidget):
|
||||
@ -242,11 +280,11 @@ class DropdownSearch(QWidget):
|
||||
|
||||
# --- WICHTIGE EINSTELLUNGEN ---
|
||||
# Ignoriert Groß-/Kleinschreibung (sehr wichtig für eine gute Suche!)
|
||||
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||||
self.completer.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
|
||||
|
||||
# 'MatchContains' sorgt dafür, dass "pha" auch "Projekt Alpha" findet.
|
||||
# Standard ist 'MatchStartsWith' (findet nur Worte am Anfang).
|
||||
self.completer.setFilterMode(Qt.MatchContains)
|
||||
self.completer.setFilterMode(Qt.MatchFlag.MatchContains)
|
||||
|
||||
# 4. Den Completer an das Eingabefeld binden
|
||||
self.search_input.setCompleter(self.completer)
|
||||
@ -261,78 +299,405 @@ class DropdownSearch(QWidget):
|
||||
# Hier könntest du z.B. deine Detail-Seite für dieses Projekt öffnen
|
||||
|
||||
|
||||
class FormFieldType(enum.StrEnum):
|
||||
TEXT = enum.auto()
|
||||
LONGTEXT = enum.auto()
|
||||
DATE = enum.auto()
|
||||
DATETIME = enum.auto()
|
||||
|
||||
|
||||
@dc.dataclass(slots=True)
|
||||
class FormField:
|
||||
key: str
|
||||
label: str
|
||||
type: FormFieldType
|
||||
required: bool
|
||||
placeholder: str | None = None
|
||||
fill_value: str | None = None
|
||||
readonly: bool = False
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.label = self.label.strip()
|
||||
if not self.label.endswith(":"):
|
||||
self.label += ":"
|
||||
if self.required:
|
||||
self.label += "*"
|
||||
|
||||
|
||||
@dc.dataclass(slots=True)
|
||||
class FormFieldGroup:
|
||||
key: str
|
||||
label: str
|
||||
fields: Sequence[FormField]
|
||||
|
||||
|
||||
FORM_FIELD_DEF = [
|
||||
FormField("name", "Projektname", FormFieldType.TEXT, True, "Bitte füllen..."),
|
||||
FormField("descr", "Beschreibung", FormFieldType.LONGTEXT, False),
|
||||
FormField(
|
||||
"ext_data",
|
||||
"Externe Daten",
|
||||
FormFieldType.TEXT,
|
||||
True,
|
||||
fill_value="Lorem ipsum und so weiter...",
|
||||
readonly=True,
|
||||
),
|
||||
FormField("init_date", "Auftragsdatum", FormFieldType.DATE, True),
|
||||
FormField("start_date", "Startdatum", FormFieldType.DATE, True),
|
||||
FormField(
|
||||
"ms_date1",
|
||||
"MS Datum 1",
|
||||
FormFieldType.DATE,
|
||||
False,
|
||||
"",
|
||||
fill_value="26.07.2026",
|
||||
readonly=True,
|
||||
),
|
||||
FormField(
|
||||
"ms_date2",
|
||||
"MS Datum 2",
|
||||
FormFieldType.DATE,
|
||||
False,
|
||||
"",
|
||||
fill_value="30.08.2026",
|
||||
readonly=False,
|
||||
),
|
||||
FormField(
|
||||
"important:notes",
|
||||
"Wichtige Notizen",
|
||||
FormFieldType.LONGTEXT,
|
||||
True,
|
||||
"Text eingeben...",
|
||||
),
|
||||
]
|
||||
|
||||
FORM_FIELD_DEF2 = [
|
||||
FormField("name", "Projektname", FormFieldType.TEXT, True, "Bitte füllen..."),
|
||||
FormField("descr", "Beschreibung", FormFieldType.LONGTEXT, False),
|
||||
FormField(
|
||||
"ext_data",
|
||||
"Externe Daten",
|
||||
FormFieldType.TEXT,
|
||||
True,
|
||||
fill_value="Lorem ipsum und so weiter...",
|
||||
readonly=True,
|
||||
),
|
||||
FormField("init_date", "Auftragsdatum", FormFieldType.DATE, True),
|
||||
FormField("start_date", "Startdatum", FormFieldType.DATE, True),
|
||||
FormField(
|
||||
"ms_date1",
|
||||
"MS Datum 1",
|
||||
FormFieldType.DATE,
|
||||
False,
|
||||
"",
|
||||
fill_value="26.07.2026",
|
||||
readonly=True,
|
||||
),
|
||||
FormField(
|
||||
"ms_date2",
|
||||
"MS Datum 2",
|
||||
FormFieldType.DATE,
|
||||
False,
|
||||
"",
|
||||
fill_value="30.08.2026",
|
||||
readonly=False,
|
||||
),
|
||||
FormField(
|
||||
"important:notes",
|
||||
"Wichtige Notizen",
|
||||
FormFieldType.LONGTEXT,
|
||||
True,
|
||||
"Text eingeben...",
|
||||
),
|
||||
]
|
||||
|
||||
FORM_FIELD_GROUPS = [
|
||||
FormFieldGroup("group1", "Test-1", FORM_FIELD_DEF),
|
||||
FormFieldGroup("group2", "Test-2", FORM_FIELD_DEF2),
|
||||
]
|
||||
|
||||
|
||||
class MyForm(QWidget):
|
||||
def __init__(self):
|
||||
def __init__(
|
||||
self,
|
||||
form_field_groups: Sequence[FormFieldGroup],
|
||||
add_buttons: bool = True,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
# --- LAYOUT ---
|
||||
self.main_layout = QVBoxLayout(self)
|
||||
self.main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.form_field_groups = form_field_groups
|
||||
|
||||
# Das Herzstück: Das Form-Layout
|
||||
# Es kümmert sich automatisch darum, dass alle Labels links
|
||||
# und alle Felder rechts perfekt bündig untereinander stehen.
|
||||
self.form_layout = QFormLayout(self)
|
||||
self.form_layout.setSpacing(15) # Abstand zwischen den Zeilen
|
||||
for fg in form_field_groups:
|
||||
widget = MyFormPart(fg.fields, fg.label, add_buttons=False)
|
||||
self.main_layout.addWidget(widget)
|
||||
|
||||
# Definition deiner Felder
|
||||
# 'key' ist der Name, unter dem du die Daten später abrufst
|
||||
# 'label' ist der Text, der angezeigt wird
|
||||
# 'type' bestimmt, welches Widget erstellt wird
|
||||
self.field_definitions = [
|
||||
{"key": "name", "label": "Projektname:", "type": "text"},
|
||||
{"key": "date", "label": "Datum:", "type": "date", "value": "22.04.2026"},
|
||||
{
|
||||
"key": "status",
|
||||
"label": "Status:",
|
||||
"type": "text",
|
||||
"placeholder": "z.B. Aktiv",
|
||||
},
|
||||
{"key": "desc", "label": "Beschreibung:", "type": "longtext"},
|
||||
{"key": "notes", "label": "Interne Notizen:", "type": "longtext"},
|
||||
]
|
||||
# buttons
|
||||
# self.add_buttons = add_buttons
|
||||
# if self.add_buttons:
|
||||
# self.layout_btn = QHBoxLayout()
|
||||
# self.main_layout.addLayout(self.layout_btn)
|
||||
# self.save_btn_txt_enabled = "Speichern (Strg + S)"
|
||||
# self.save_btn_txt_disabled = "Wird gespeichert..."
|
||||
# self.save_btn = QPushButton(self.save_btn_txt_enabled)
|
||||
# self.save_btn.setShortcut("Ctrl+S")
|
||||
# self.save_btn.setFixedHeight(50)
|
||||
# self.save_btn.setSizePolicy(
|
||||
# QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
|
||||
# )
|
||||
# self.save_btn.clicked.connect(self.on_save_clicked)
|
||||
# self.layout_btn.addWidget(self.save_btn)
|
||||
# self.reset_btn = QPushButton("Zurücksetzen (Strg + Z)")
|
||||
# self.reset_btn.setShortcut("Ctrl+Z")
|
||||
# self.reset_btn.setFixedHeight(50)
|
||||
# self.reset_btn.setSizePolicy(
|
||||
# QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
|
||||
# )
|
||||
# self.reset_btn.clicked.connect(self.reset_form)
|
||||
# self.layout_btn.addWidget(self.reset_btn)
|
||||
|
||||
# Dictionary, um die erstellten Widgets zu speichern (für späteren Zugriff)
|
||||
self.widgets = {}
|
||||
|
||||
# Automatischer Aufbau des Formulars
|
||||
class MyFormPart(QWidget):
|
||||
def __init__(
|
||||
self,
|
||||
field_definitons: Sequence[FormField],
|
||||
group_name: str | None = None,
|
||||
add_buttons: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.setStyleSheet("""
|
||||
QGroupBox {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
border: 1px solid #cbd5e1; /* Heller, moderner Rahmen */
|
||||
border-radius: 8px; /* Abgerundete Ecken */
|
||||
margin-top: 15px; /* Platz für die Überschrift schaffen */
|
||||
padding-top: 15px; /* Abstand zwischen Rahmen und erstem Feld */
|
||||
}
|
||||
|
||||
QGroupBox::title {
|
||||
subcontrol-origin: margin;
|
||||
subcontrol-position: top left; /* Überschrift oben links */
|
||||
padding: 0 5px; /* Etwas Luft links und rechts vom Text */
|
||||
color: #334155; /* Dunkelgraue Schrift */
|
||||
}
|
||||
""")
|
||||
|
||||
# --- LAYOUT ---
|
||||
self.main_layout = QVBoxLayout(self)
|
||||
self.main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.form_layout = QFormLayout()
|
||||
self.group_box: QGroupBox | None = None
|
||||
self.group_name = group_name
|
||||
|
||||
if self.group_name:
|
||||
self.group_box = QGroupBox(group_name)
|
||||
self.main_layout.addWidget(self.group_box)
|
||||
self.group_box.setLayout(self.form_layout)
|
||||
else:
|
||||
self.main_layout.addLayout(self.form_layout)
|
||||
|
||||
self.form_layout.setSpacing(10) # Abstand zwischen den Zeilen
|
||||
|
||||
self.field_definitions = field_definitons
|
||||
self.widgets: dict[str, QWidget] = {}
|
||||
# automatic build
|
||||
self.create_form_fields()
|
||||
|
||||
def create_form_fields(self):
|
||||
# buttons
|
||||
self.add_buttons = add_buttons
|
||||
if self.add_buttons:
|
||||
self.layout_btn = QHBoxLayout()
|
||||
self.main_layout.addLayout(self.layout_btn)
|
||||
self.save_btn_txt_enabled = "Speichern (Strg + S)"
|
||||
self.save_btn_txt_disabled = "Wird gespeichert..."
|
||||
self.save_btn = QPushButton(self.save_btn_txt_enabled)
|
||||
self.save_btn.setShortcut("Ctrl+S")
|
||||
self.save_btn.setFixedHeight(50)
|
||||
self.save_btn.setSizePolicy(
|
||||
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
|
||||
)
|
||||
self.save_btn.clicked.connect(self.on_save_clicked)
|
||||
self.layout_btn.addWidget(self.save_btn)
|
||||
self.reset_btn = QPushButton("Zurücksetzen (Strg + Z)")
|
||||
self.reset_btn.setShortcut("Ctrl+Z")
|
||||
self.reset_btn.setFixedHeight(50)
|
||||
self.reset_btn.setSizePolicy(
|
||||
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
|
||||
)
|
||||
self.reset_btn.clicked.connect(self.reset_form)
|
||||
self.layout_btn.addWidget(self.reset_btn)
|
||||
|
||||
def create_form_fields(self) -> None:
|
||||
for field in self.field_definitions:
|
||||
widget = None
|
||||
|
||||
# Entscheidung: Welches Widget wird benötigt?
|
||||
if field["type"] == "text":
|
||||
widget = QLineEdit()
|
||||
if "placeholder" in field:
|
||||
widget.setPlaceholderText(field["placeholder"])
|
||||
if "value" in field:
|
||||
widget.setText(field["value"])
|
||||
widget.setReadOnly(True) # Falls es ein Fixwert ist
|
||||
match field.type:
|
||||
case FormFieldType.TEXT:
|
||||
widget = QLineEdit()
|
||||
if field.placeholder:
|
||||
widget.setPlaceholderText(field.placeholder)
|
||||
if field.fill_value:
|
||||
widget.setText(field.fill_value)
|
||||
if field.readonly:
|
||||
widget.setReadOnly(True) # Falls es ein Fixwert ist
|
||||
widget.setProperty("styleClass", "stempel")
|
||||
|
||||
elif field["type"] == "longtext":
|
||||
widget = QPlainTextEdit()
|
||||
widget.setMaximumHeight(80) # Kompakte Höhe für Formulare
|
||||
case FormFieldType.LONGTEXT:
|
||||
widget = QPlainTextEdit()
|
||||
widget.setMaximumHeight(80) # Kompakte Höhe für Formulare
|
||||
if field.placeholder:
|
||||
widget.setPlaceholderText(field.placeholder)
|
||||
if field.readonly:
|
||||
widget.setReadOnly(True) # Falls es ein Fixwert ist
|
||||
widget.setProperty("styleClass", "stempel")
|
||||
|
||||
elif field["type"] == "date":
|
||||
widget = QLineEdit() # Oder QDateEdit
|
||||
widget.setText(field.get("value", ""))
|
||||
widget.setReadOnly(True)
|
||||
widget.setStyleSheet("background-color: #f1f5f9; border: 1px dashed #cbd5e1;")
|
||||
case FormFieldType.DATE:
|
||||
widget = QDateEdit() # Oder QDateEdit
|
||||
widget.setCalendarPopup(True)
|
||||
cal = widget.calendarWidget()
|
||||
if cal:
|
||||
cal.setFirstDayOfWeek(Qt.DayOfWeek.Monday)
|
||||
cal.setGridVisible(True)
|
||||
cal.setStyleSheet("""
|
||||
QCalendarWidget QAbstractItemView {
|
||||
selection-background-color: #2980b9;
|
||||
selection-color: white;
|
||||
}
|
||||
""")
|
||||
if field.fill_value:
|
||||
set_date = QDate.fromString(field.fill_value, "dd.MM.yyyy")
|
||||
if not set_date.isValid():
|
||||
raise ValueError(
|
||||
f"Could not parse date field value >{field.fill_value}<"
|
||||
)
|
||||
widget.setDate(set_date)
|
||||
else:
|
||||
widget.setDate(QDate.currentDate())
|
||||
|
||||
if field.readonly:
|
||||
widget = QLineEdit()
|
||||
widget.setReadOnly(True) # Falls es ein Fixwert ist
|
||||
widget.setProperty("styleClass", "stempel")
|
||||
if field.fill_value:
|
||||
widget.setText(field.fill_value)
|
||||
|
||||
case _:
|
||||
raise NotImplementedError(f"Not supported field type: {field.type.value}")
|
||||
|
||||
if widget:
|
||||
# Widget im Dictionary speichern, um später darauf zuzugreifen
|
||||
self.widgets[field["key"]] = widget
|
||||
# Dem Form-Layout hinzufügen (Label links, Widget rechts)
|
||||
self.form_layout.addRow(field["label"], widget)
|
||||
self.widgets[field.key] = widget
|
||||
self.form_layout.addRow(field.label, widget)
|
||||
|
||||
def get_form_data(self):
|
||||
def get_form_data(self) -> ...:
|
||||
"""Liest alle Felder automatisch aus"""
|
||||
data = {}
|
||||
for key, widget in self.widgets.items():
|
||||
if isinstance(widget, QLineEdit):
|
||||
if isinstance(widget, (QLineEdit, QDateEdit)):
|
||||
data[key] = widget.text()
|
||||
elif isinstance(widget, QPlainTextEdit):
|
||||
data[key] = widget.toPlainText()
|
||||
|
||||
return data
|
||||
|
||||
def _disable_save(self) -> None:
|
||||
self.save_btn.setEnabled(False)
|
||||
self.save_btn.setText(self.save_btn_txt_disabled)
|
||||
|
||||
def _enable_save(
|
||||
self,
|
||||
timeout: int = 3000,
|
||||
) -> None:
|
||||
QTimer.singleShot(timeout, lambda: self.save_btn.setEnabled(True))
|
||||
QTimer.singleShot(timeout + 1, lambda: self.save_btn.setShortcut("Ctrl+S"))
|
||||
self.save_btn.setText(self.save_btn_txt_enabled)
|
||||
|
||||
def on_save_clicked(self) -> None:
|
||||
self._disable_save()
|
||||
errors = [] # Hier sammeln wir die Namen der fehlenden Felder
|
||||
|
||||
# time.sleep(0.5)
|
||||
|
||||
# Wir gehen unsere Feld-Definitionen durch
|
||||
for field in self.field_definitions:
|
||||
widget = self.widgets[field.key]
|
||||
|
||||
# 1. Zuerst setzen wir das Design des Feldes wieder auf "Normal" zurück.
|
||||
# Falls der Nutzer den Fehler vorher schon korrigiert hat, muss der rote Rand weg!
|
||||
if not field.readonly:
|
||||
widget.setStyleSheet("")
|
||||
|
||||
# 2. Ist es überhaupt ein Pflichtfeld?
|
||||
if not field.required:
|
||||
continue
|
||||
|
||||
is_empty = False
|
||||
if isinstance(widget, (QLineEdit, QDateEdit)):
|
||||
if not widget.text().strip():
|
||||
is_empty = True
|
||||
|
||||
elif isinstance(widget, QPlainTextEdit):
|
||||
if not widget.toPlainText().strip():
|
||||
is_empty = True
|
||||
|
||||
if not is_empty:
|
||||
continue
|
||||
|
||||
errors.append(
|
||||
field.label.replace("*", "").replace(":", "")
|
||||
) # Sternchen für die Fehlermeldung entfernen
|
||||
|
||||
# Optisches Feedback: Heller roter Hintergrund und roter Rand
|
||||
widget.setStyleSheet("""
|
||||
border: 1px solid #ef4444;
|
||||
background-color: #ffe9e9;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
""")
|
||||
|
||||
# --- ERGEBNIS AUSWERTEN ---
|
||||
if errors:
|
||||
# Es gibt Fehler! Speichern abbrechen und Pop-up anzeigen.
|
||||
error_text = "Bitte fülle die folgenden Pflichtfelder aus:\n\n- " + "\n- ".join(
|
||||
errors
|
||||
)
|
||||
QMessageBox.warning(self, "Fehlende Angaben", error_text)
|
||||
self._enable_save()
|
||||
return
|
||||
|
||||
# Wenn wir hier ankommen, ist die Liste 'errors' leer. Alles ist korrekt ausgefüllt!
|
||||
# time.sleep(0.5)
|
||||
print("Erfolg! Alle Daten sind valide.")
|
||||
self.reset_form()
|
||||
self._enable_save()
|
||||
|
||||
def reset_form(self) -> None:
|
||||
for field in self.field_definitions:
|
||||
widget = self.widgets[field.key]
|
||||
|
||||
if field.readonly:
|
||||
continue
|
||||
|
||||
if isinstance(widget, QLineEdit):
|
||||
widget.clear()
|
||||
if field.fill_value:
|
||||
widget.setText(field.fill_value)
|
||||
elif isinstance(widget, QPlainTextEdit):
|
||||
widget.clear()
|
||||
elif isinstance(widget, QDateEdit):
|
||||
if field.fill_value:
|
||||
set_date = QDate.fromString(field.fill_value, "dd.MM.yyyy")
|
||||
if not set_date.isValid():
|
||||
raise ValueError(
|
||||
f"Could not parse date field value >{field.fill_value}<"
|
||||
)
|
||||
widget.setDate(set_date)
|
||||
else:
|
||||
widget.setDate(QDate.currentDate())
|
||||
|
||||
widget.setStyleSheet("")
|
||||
|
||||
|
||||
class ClickableCell(QFrame):
|
||||
# Wir definieren ein Signal, das ein Dictionary (die Daten) mitschickt
|
||||
@ -355,11 +720,11 @@ class ClickableCell(QFrame):
|
||||
layout = QVBoxLayout(self)
|
||||
label = QLabel(text)
|
||||
label.setWordWrap(True)
|
||||
label.setAlignment(Qt.AlignCenter)
|
||||
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
layout.addWidget(label)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() == Qt.LeftButton:
|
||||
if event.button() == Qt.MouseButton.LeftButton:
|
||||
# Wenn geklickt wird, senden wir die Daten aus
|
||||
self.clicked.emit(self.data_record)
|
||||
|
||||
@ -369,7 +734,7 @@ class HeaderCell(QLabel):
|
||||
super().__init__(text)
|
||||
|
||||
# Textausrichtung zentrieren
|
||||
self.setAlignment(Qt.AlignCenter)
|
||||
self.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
# Styling: Fetter Text, grauer Hintergrund (entspricht slate-200), leicht abgerundet
|
||||
self.setStyleSheet("""
|
||||
@ -407,7 +772,9 @@ class NewEntryDialog(QDialog):
|
||||
layout.addRow("Datum:", self.input_date)
|
||||
|
||||
# Standard-Buttons (OK und Abbrechen)
|
||||
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
buttons = QDialogButtonBox(
|
||||
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||
)
|
||||
buttons.accepted.connect(self.accept) # Schließt Dialog und meldet "Erfolg"
|
||||
buttons.rejected.connect(self.reject) # Schließt Dialog und meldet "Abbruch"
|
||||
layout.addWidget(buttons)
|
||||
@ -431,7 +798,7 @@ class DetailView(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setAlignment(Qt.AlignTop | Qt.AlignLeft)
|
||||
layout.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft)
|
||||
|
||||
# Zurück-Button
|
||||
back_btn = QPushButton("← Zurück zur Tabelle")
|
||||
@ -470,7 +837,7 @@ class NewEntrySelect_view(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setAlignment(Qt.AlignTop | Qt.AlignLeft)
|
||||
layout.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft)
|
||||
|
||||
# Zurück-Button
|
||||
back_btn = QPushButton("← Zurück zur Übersicht")
|
||||
@ -524,38 +891,65 @@ class SearchFormPage(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# Hauptlayout der Seite
|
||||
main_layout = QVBoxLayout(self)
|
||||
outer_layout = QHBoxLayout(self)
|
||||
vert_layout = QVBoxLayout()
|
||||
# main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
outer_layout.addStretch(1)
|
||||
outer_layout.addLayout(vert_layout, stretch=100)
|
||||
# outer_layout.addWidget(scroll_area, stretch=100)
|
||||
outer_layout.addStretch(1)
|
||||
# Optional: Damit der Container oben am Rand klebt
|
||||
outer_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
# --- 1. HEADER ---
|
||||
header_layout = QVBoxLayout()
|
||||
upper_button_group_v = QVBoxLayout()
|
||||
upper_button_group = QHBoxLayout()
|
||||
upper_button_group.addLayout(upper_button_group_v)
|
||||
upper_button_group.addStretch()
|
||||
# header_layout = QGridLayout()
|
||||
# header_layout.setColumnStretch(0, 1)
|
||||
# header_layout.setColumnStretch(1, 1)
|
||||
# --- HEADER ---
|
||||
header_container = QWidget()
|
||||
header_container.setMinimumWidth(700)
|
||||
header_container.setMaximumWidth(1000)
|
||||
header_container.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
header_layout = QVBoxLayout(header_container)
|
||||
header_layout.setContentsMargins(0, 0, 0, 10)
|
||||
|
||||
back_btn_main = QPushButton("← Zurück zur Übersicht")
|
||||
back_btn_main.clicked.connect(lambda: self.back_main_requested.emit())
|
||||
back_btn_main.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||
back_btn_main.setMinimumWidth(200)
|
||||
back_btn_main.setMaximumWidth(200)
|
||||
back_btn_step = QPushButton("← Zurück")
|
||||
back_btn_step.clicked.connect(lambda: self.back_requested.emit())
|
||||
back_btn_step.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||
back_btn_step.setMinimumWidth(200)
|
||||
back_btn_step.setMaximumWidth(200)
|
||||
|
||||
title = QLabel("Grunderfassung Unternehmen")
|
||||
title.setStyleSheet("font-size: 20px; font-weight: bold;")
|
||||
|
||||
upper_button_group_v.addWidget(back_btn_step)
|
||||
upper_button_group_v.addWidget(back_btn_main)
|
||||
|
||||
header_layout.addLayout(upper_button_group)
|
||||
header_layout.addSpacing(15)
|
||||
# header_layout.addWidget(back_btn_step)
|
||||
header_layout.setSpacing(5)
|
||||
header_layout.addWidget(back_btn_step)
|
||||
header_layout.addWidget(back_btn_main)
|
||||
header_layout.addWidget(title)
|
||||
# header_layout.addStretch() # Drückt den Titel nach links
|
||||
main_layout.addLayout(header_layout)
|
||||
vert_layout.addWidget(header_container)
|
||||
|
||||
# --- HAUPTINHALT ---
|
||||
container = QWidget()
|
||||
# SCROLL-BEREICH
|
||||
scroll_area = QScrollArea()
|
||||
scroll_area.setWidgetResizable(
|
||||
True
|
||||
) # WICHTIG: Erlaubt dem Grid im Inneren, sich an die Breite anzupassen
|
||||
scroll_area.setMinimumWidth(700)
|
||||
scroll_area.setMaximumWidth(1000)
|
||||
scroll_area.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
# Optional: Rahmen der ScrollArea entfernen, damit es "flacher" und moderner aussieht
|
||||
scroll_area.setFrameShape(QFrame.Shape.NoFrame)
|
||||
scroll_area.setWidget(container)
|
||||
|
||||
# vert_layout.addSpacing(20)
|
||||
vert_layout.addWidget(scroll_area)
|
||||
|
||||
# --- KOPF Metadaten ---
|
||||
container_layout = QVBoxLayout(container)
|
||||
container_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# --- KOPF Unternehmen ---
|
||||
inf_block_1 = QHBoxLayout()
|
||||
inhalte = [
|
||||
"Fall-Nr.:",
|
||||
@ -565,7 +959,7 @@ class SearchFormPage(QWidget):
|
||||
]
|
||||
for entry in inhalte:
|
||||
label = QLabel(entry)
|
||||
label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||
label.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||
field = QLineEdit(placeholderText="...")
|
||||
field.setText("22.04.2026")
|
||||
field.setReadOnly(True)
|
||||
@ -582,107 +976,47 @@ class SearchFormPage(QWidget):
|
||||
border: 1px #cbd5e1;
|
||||
}
|
||||
""")
|
||||
field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
field.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
inf_block_1.addWidget(label)
|
||||
inf_block_1.addWidget(field)
|
||||
|
||||
inf_block_1.addStretch()
|
||||
main_layout.addLayout(inf_block_1)
|
||||
container_layout.addLayout(inf_block_1)
|
||||
|
||||
# --- NOTIZEN Unternehmen ---
|
||||
# eventuell später verknüpft
|
||||
inf_block_2 = QHBoxLayout()
|
||||
label = QLabel("Notizen:")
|
||||
label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||
label.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||
inf_block_2.addWidget(label, alignment=Qt.AlignmentFlag.AlignTop)
|
||||
inf_block_2.addWidget(QPlainTextEdit(placeholderText="Notizen ergänzen..."))
|
||||
|
||||
main_layout.addLayout(inf_block_2)
|
||||
main_layout.addSpacing(10)
|
||||
container_layout.addLayout(inf_block_2)
|
||||
container_layout.addSpacing(10)
|
||||
|
||||
# --- Suche mit Namen
|
||||
# inf_block_3 = (
|
||||
# QVBoxLayout()
|
||||
# ) # Horizontal, damit Suchen und Filtern nebeneinander stehen
|
||||
# name_input = QLineEdit(placeholderText="Tippe zum Suchen...")
|
||||
# name_input.setMinimumWidth(150)
|
||||
# name_input.setMaximumWidth(600)
|
||||
# inf_block_3_1 = QHBoxLayout()
|
||||
# inf_block_3_1.addWidget(QLabel("Name"))
|
||||
# inf_block_3_1.addWidget(name_input, stretch=100)
|
||||
# inf_block_3_1.addStretch()
|
||||
# --- Test Formularlayout ---
|
||||
container_layout.addSpacing(30)
|
||||
title = QLabel("--- Automatische Form ---")
|
||||
title.setStyleSheet("font-size: 14px; font-style: italic;") # font-weight: bold;
|
||||
container_layout.addWidget(title)
|
||||
# container_layout.addWidget(MyFormPart(FORM_FIELD_DEF, "Test-Gruppe"))
|
||||
container_layout.addWidget(MyForm(FORM_FIELD_GROUPS))
|
||||
|
||||
# inf_block_3_2 = QHBoxLayout()
|
||||
# inf_block_3_3 = QHBoxLayout()
|
||||
# demo_data = {
|
||||
# "name": "Test UG",
|
||||
# "Straße": "Teststraße",
|
||||
# "Hausnummer": "12",
|
||||
# "PLZ": "09111",
|
||||
# "Ort": "Chemnitz",
|
||||
# }
|
||||
|
||||
# current_block = inf_block_3_2
|
||||
# for entry in ("Straße", "Hausnummer", "PLZ", "Ort"):
|
||||
# if entry == "PLZ":
|
||||
# current_block = inf_block_3_3
|
||||
|
||||
# label = QLabel(entry)
|
||||
# label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||
# field = QLineEdit()
|
||||
# field.setText(demo_data[entry])
|
||||
# field.setReadOnly(True)
|
||||
# field.setStyleSheet("""
|
||||
# QLineEdit {
|
||||
# background-color: #f1f5f9; /* Helles System-Grau */
|
||||
# color: #333D4B; /* Etwas blassere Schrift */
|
||||
# border: 1px dashed #cbd5e1; /* Ein gestrichelter Rand wirkt oft wie ein "Stempel" */
|
||||
# border-radius: 4px;
|
||||
# padding: 5px;
|
||||
# }
|
||||
# /* Wenn das Feld fokussiert wird, keinen blauen Rand anzeigen */
|
||||
# QLineEdit:focus {
|
||||
# border: 1px dashed #cbd5e1;
|
||||
# }
|
||||
# """)
|
||||
# if entry in ("Hausnummer", "PLZ"):
|
||||
# field.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||
# field.setMinimumWidth(50)
|
||||
# field.setMaximumWidth(50)
|
||||
# else:
|
||||
# field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
# field.setMinimumWidth(100)
|
||||
# field.setMaximumWidth(300)
|
||||
# current_block.addWidget(label)
|
||||
# current_block.addWidget(field, stretch=100)
|
||||
|
||||
# inf_block_3_2.addStretch()
|
||||
# inf_block_3_3.addStretch()
|
||||
|
||||
# inf_block_3.addLayout(inf_block_3_1)
|
||||
# inf_block_3.addLayout(inf_block_3_2)
|
||||
# inf_block_3.addLayout(inf_block_3_3)
|
||||
# main_layout.addLayout(inf_block_3)
|
||||
|
||||
main_layout.addSpacing(30)
|
||||
|
||||
main_layout.addWidget(MyForm())
|
||||
|
||||
main_layout.addSpacing(30)
|
||||
container_layout.addSpacing(30)
|
||||
|
||||
# --- SUCHE MIT NAMEN ---
|
||||
# addr = Address("Test UG", "Teststraße", 202, "09111", "Chemnitz")
|
||||
# addr_widget = AddressForm()
|
||||
# addr_widget.fill_out(addr)
|
||||
addr_widget = AddressForm_Search()
|
||||
main_layout.addWidget(addr_widget)
|
||||
container_layout.addWidget(addr_widget)
|
||||
|
||||
main_layout.addSpacing(30)
|
||||
container_layout.addSpacing(30)
|
||||
|
||||
main_layout.addWidget(DropdownSearch())
|
||||
# container_layout.addWidget(DropdownSearch())
|
||||
|
||||
main_layout.addSpacing(30)
|
||||
container_layout.addSpacing(30)
|
||||
|
||||
# --- 2. SUCH-FORMULAR ---
|
||||
# --- SUCH-FORMULAR ---
|
||||
form_layout = (
|
||||
QHBoxLayout()
|
||||
) # Horizontal, damit Suchen und Filtern nebeneinander stehen
|
||||
@ -702,12 +1036,12 @@ class SearchFormPage(QWidget):
|
||||
form_layout.addWidget(QLabel("Status:"))
|
||||
form_layout.addWidget(self.status_filter, stretch=1)
|
||||
|
||||
main_layout.addLayout(form_layout)
|
||||
container_layout.addLayout(form_layout)
|
||||
|
||||
# --- 3. ERGEBNIS-BEREICH ---
|
||||
# Für den Anfang ein einfaches Listen-Widget
|
||||
self.results_list = QListWidget()
|
||||
main_layout.addWidget(
|
||||
container_layout.addWidget(
|
||||
self.results_list, stretch=100
|
||||
) # Nimmt den restlichen Platz ein
|
||||
|
||||
@ -740,7 +1074,7 @@ class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("Master")
|
||||
self.resize(1800, 200)
|
||||
self.resize(1800, 1000)
|
||||
|
||||
# --- 1. DAS MENÜ ERSTELLEN ---
|
||||
self.create_menu()
|
||||
@ -801,9 +1135,9 @@ class MainWindow(QMainWindow):
|
||||
scroll_area.setMaximumWidth(
|
||||
1500
|
||||
) # Die Breiten-Begrenzung wandert nun auf die ScrollArea
|
||||
scroll_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
scroll_area.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
# Optional: Rahmen der ScrollArea entfernen, damit es "flacher" und moderner aussieht
|
||||
scroll_area.setFrameShape(QFrame.NoFrame)
|
||||
scroll_area.setFrameShape(QFrame.Shape.NoFrame)
|
||||
scroll_area.setWidget(container)
|
||||
|
||||
vert_layout.addWidget(new_btn)
|
||||
@ -817,7 +1151,7 @@ class MainWindow(QMainWindow):
|
||||
# outer_layout.addWidget(scroll_area, stretch=100)
|
||||
outer_layout.addStretch(1)
|
||||
# Optional: Damit der Container oben am Rand klebt
|
||||
outer_layout.setAlignment(Qt.AlignTop)
|
||||
outer_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
# Wir geben dem Container ein vertikales Layout
|
||||
container_layout = QVBoxLayout(container)
|
||||
@ -961,6 +1295,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
app.setStyleSheet(QSS)
|
||||
window = MainWindow()
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
|
||||
@ -1,28 +1,45 @@
|
||||
# %%
|
||||
import dataclasses as dc
|
||||
import enum
|
||||
|
||||
from PySide6.QtCore import QDate, Qt
|
||||
|
||||
|
||||
class FormFieldType(enum.StrEnum):
|
||||
TEXT = enum.auto()
|
||||
LONGTEXT = enum.auto()
|
||||
DATE = enum.auto()
|
||||
DATETIME = enum.auto()
|
||||
|
||||
|
||||
# %%
|
||||
@dc.dataclass(slots=True)
|
||||
class Address:
|
||||
street: str
|
||||
number: int
|
||||
postal_code: str
|
||||
city: str
|
||||
class FormField:
|
||||
key: str
|
||||
label: str
|
||||
type: FormFieldType
|
||||
required: bool
|
||||
|
||||
def export(self):
|
||||
data = {}
|
||||
for f in dc.fields(self):
|
||||
val = getattr(self, f.name)
|
||||
if f.type is int:
|
||||
val = str(val)
|
||||
data[f.name] = val
|
||||
|
||||
return data
|
||||
def __post_init__(self) -> None:
|
||||
self.label = self.label.strip()
|
||||
if not self.label.endswith(":"):
|
||||
self.label += ":"
|
||||
if self.required:
|
||||
self.label += "*"
|
||||
|
||||
|
||||
# %%
|
||||
addr = Address("Teststraße", 202, "09111", "Chemnitz")
|
||||
FormField("name", "Projektbeschreibung", FormFieldType.LONGTEXT, required=True)
|
||||
# %%
|
||||
FormField("name", "Projektbeschreibung:", FormFieldType.LONGTEXT, required=True)
|
||||
|
||||
# %%
|
||||
FormField("name", "Projektbeschreibung", FormFieldType.LONGTEXT, required=False)
|
||||
# %%
|
||||
FormField("name", "Projektbeschreibung:", FormFieldType.LONGTEXT, required=False)
|
||||
# %%
|
||||
addr.export()
|
||||
# %%
|
||||
set_date = QDate.fromString("26.07.2026", "dd.MM.yyyy")
|
||||
|
||||
# %%
|
||||
Qt.Tet
|
||||
|
||||
@ -5,7 +5,7 @@ description = "GUI for CRM of NAFKA project with WCE"
|
||||
authors = [
|
||||
{name = "d-opt GmbH, resp. Florian Förster", email = "f.foerster@d-opt.com"},
|
||||
]
|
||||
dependencies = ["nicegui>=3.10.0", "pyside6>=6.11.0"]
|
||||
dependencies = ["nicegui>=3.10.0", "pyside6>=6.11.0", "sqlalchemy>=2.0.49", "polars>=1.40.1", "dopt-basics>=0.2.4"]
|
||||
requires-python = "<3.14,>=3.11"
|
||||
readme = "README.md"
|
||||
license = {text = "LicenseRef-Proprietary"}
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
import wce_crm.env
|
||||
|
||||
wce_crm.env.setup()
|
||||
0
src/wce_crm/backend/__init__.py
Normal file
0
src/wce_crm/backend/__init__.py
Normal file
78
src/wce_crm/backend/initial_recording.py
Normal file
78
src/wce_crm/backend/initial_recording.py
Normal file
@ -0,0 +1,78 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TypedDict, cast
|
||||
|
||||
import polars as pl
|
||||
|
||||
from wce_crm import db
|
||||
|
||||
|
||||
class CompanyInfo(TypedDict):
|
||||
ma_id: str
|
||||
wce_id: str
|
||||
ma_unternehmensname: str
|
||||
ma_branche: str
|
||||
ma_strasse: str
|
||||
ma_hausnummer: str
|
||||
ma_plz: str
|
||||
ma_ort: str
|
||||
ma_plz_postfach: str
|
||||
ma_postfach: str
|
||||
ma_website: str
|
||||
ma_mail: str
|
||||
ma_telefonnummer: str
|
||||
ma_faxnummer: str
|
||||
ma_ersteintrag_datum: str
|
||||
ma_aktualisierung_datum: str
|
||||
ma_aktualisierung_nutzer: str
|
||||
ma_sollprozess: str
|
||||
ma_auslaendische_mitarbeiter: str
|
||||
ma_quelle_information: str
|
||||
ma_bemerkung: str
|
||||
ma_kontakt: str
|
||||
ma_schlagworte: str
|
||||
ma_archiviert: str
|
||||
|
||||
|
||||
def _transform_for_gui_output(
|
||||
data: pl.DataFrame,
|
||||
) -> pl.DataFrame:
|
||||
q = (
|
||||
data.lazy()
|
||||
.with_columns(
|
||||
pl.col(pl.Datetime).dt.to_string("%d.%m.%Y"),
|
||||
pl.col(pl.Date).dt.to_string("%d.%m.%Y"),
|
||||
pl.when(pl.col(pl.Boolean))
|
||||
.then(pl.lit("Ja"))
|
||||
.otherwise(pl.lit("Nein"))
|
||||
.name.keep(),
|
||||
)
|
||||
.with_columns(pl.all().cast(pl.String))
|
||||
)
|
||||
|
||||
return q.collect()
|
||||
|
||||
|
||||
def comp_search_choice_mapping() -> dict[str, int]:
|
||||
# TODO no reload functionality
|
||||
q = db.df_crm_master.lazy()
|
||||
counter = pl.int_range(0, pl.len()).over(pl.col.ma_unternehmensname)
|
||||
q = q.with_columns(
|
||||
ma_unternehmensname_dedupl=pl.when(counter == 0)
|
||||
.then(pl.col.ma_unternehmensname)
|
||||
.otherwise(pl.format("{} ({})", pl.col.ma_unternehmensname, counter))
|
||||
)
|
||||
df = q.collect()
|
||||
|
||||
return dict(zip(df["ma_unternehmensname_dedupl"], df["ma_id"]))
|
||||
|
||||
|
||||
def comp_search_get_info(
|
||||
ma_id: int,
|
||||
) -> CompanyInfo:
|
||||
df = db.df_crm_master.filter(pl.col.ma_id == ma_id)
|
||||
if df.height > 1 or df.height == 0:
|
||||
raise ValueError(f"Größe des zurückgelieferten Datenpakets ungültig: {df.height}")
|
||||
|
||||
df = _transform_for_gui_output(df)
|
||||
return cast(CompanyInfo, df.row(0, named=True))
|
||||
213
src/wce_crm/db.py
Normal file
213
src/wce_crm/db.py
Normal file
@ -0,0 +1,213 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import polars as pl
|
||||
import sqlalchemy as sql
|
||||
from sqlalchemy import Column, String, Table, TypeDecorator
|
||||
|
||||
from wce_crm import types as t
|
||||
|
||||
|
||||
class SafeDateTime(TypeDecorator):
|
||||
"""Cleans non-standard ISO strings before parsing."""
|
||||
|
||||
impl = String # We treat the underlying data as a String first
|
||||
|
||||
def process_result_value(self, value, dialect):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
# 1. Remove the trailing 'ff' (or any trailing letters)
|
||||
# 2. Replace comma with dot (SQLAlchemy prefers . over ,)
|
||||
clean_value = re.sub(r"[a-zA-Z]+$", "", value).replace(",", ".")
|
||||
|
||||
try:
|
||||
return datetime.fromisoformat(clean_value)
|
||||
except ValueError:
|
||||
# Fallback if it's still weird
|
||||
return None
|
||||
|
||||
|
||||
md_kontaktliste = sql.MetaData()
|
||||
md_crm = sql.MetaData()
|
||||
|
||||
ext_kl_unternehmen: sql.Table = Table(
|
||||
"Unternehmen",
|
||||
md_kontaktliste,
|
||||
Column("u_id", sql.Integer, nullable=False, unique=True),
|
||||
Column("u_zeitstempel_eintrag", sql.DateTime, nullable=False),
|
||||
Column("u_rechtsform", sql.Text, nullable=False),
|
||||
Column("u_firmenname", sql.Text, nullable=False),
|
||||
Column("u_strasse", sql.Text, nullable=False),
|
||||
Column("u_hausnummer", sql.Text, nullable=False),
|
||||
Column("u_adresszusatz", sql.Text, nullable=True),
|
||||
Column("u_plz", sql.Text, nullable=False),
|
||||
Column("u_ort", sql.Text, nullable=False),
|
||||
Column("u_postfach", sql.Text, nullable=True),
|
||||
Column("u_website", sql.Text, nullable=True),
|
||||
Column("u_anrede", sql.Text, nullable=False),
|
||||
Column("u_titel", sql.Text, nullable=True),
|
||||
Column("u_vorname", sql.Text, nullable=False),
|
||||
Column("u_nachname", sql.Text, nullable=False),
|
||||
Column("u_funktion", sql.Text, nullable=False),
|
||||
Column("u_mail", sql.Text, nullable=False),
|
||||
Column("u_telefon", sql.Text, nullable=False),
|
||||
Column("u_plz_postfach", sql.Text, nullable=True),
|
||||
Column("u_einwilligung_inhaber", sql.Boolean, nullable=True),
|
||||
Column("u_einwilligung_ansprechpartner", sql.Boolean, nullable=True),
|
||||
Column("u_aktiv", sql.Boolean, nullable=False, default=1),
|
||||
)
|
||||
|
||||
ext_kl_unternehmen_schema: t.PolarsSchema = {
|
||||
"u_id": pl.UInt64,
|
||||
"u_zeitstempel_eintrag": pl.Datetime,
|
||||
"u_rechtsform": pl.String,
|
||||
"u_firmenname": pl.String,
|
||||
"u_strasse": pl.String,
|
||||
"u_hausnummer": pl.String,
|
||||
"u_adresszusatz": pl.String,
|
||||
"u_plz": pl.String,
|
||||
"u_ort": pl.String,
|
||||
"u_postfach": pl.String,
|
||||
"u_website": pl.String,
|
||||
"u_anrede": pl.String,
|
||||
"u_titel": pl.String,
|
||||
"u_vorname": pl.String,
|
||||
"u_nachname": pl.String,
|
||||
"u_funktion": pl.String,
|
||||
"u_mail": pl.String,
|
||||
"u_telefon": pl.String,
|
||||
"u_plz_postfach": pl.String,
|
||||
"u_einwilligung_inhaber": pl.Boolean,
|
||||
"u_einwilligung_ansprechpartner": pl.Boolean,
|
||||
"u_aktiv": pl.Boolean,
|
||||
}
|
||||
|
||||
|
||||
def get_ext_kontaktliste(
|
||||
db_path: Path | None,
|
||||
) -> pl.DataFrame:
|
||||
if db_path is None:
|
||||
ENV_PTH = os.environ.get("DOPT_DB_KONTAKTLISTE", None)
|
||||
if ENV_PTH is None:
|
||||
raise ValueError("No database path provided or found as ENV var.")
|
||||
db_path = Path(ENV_PTH)
|
||||
|
||||
if not db_path.exists():
|
||||
raise FileNotFoundError(f"Database not found under >{db_path}<")
|
||||
|
||||
engine = sql.create_engine(f"sqlite:///{db_path}")
|
||||
stmt = sql.select(ext_kl_unternehmen)
|
||||
return pl.read_database(stmt, engine, schema_overrides=ext_kl_unternehmen_schema)
|
||||
|
||||
|
||||
df_kontaktliste = get_ext_kontaktliste(None)
|
||||
|
||||
ext_crm_master: sql.Table = Table(
|
||||
"Master",
|
||||
md_crm,
|
||||
Column("ma_id", sql.Integer, nullable=False, unique=True),
|
||||
Column("wce_id", sql.ForeignKey("Nutzer.wce_id")),
|
||||
Column("ma_unternehmensname", sql.Text, nullable=True),
|
||||
Column("ma_branche", sql.Text, nullable=True),
|
||||
Column("ma_strasse", sql.Text, nullable=True),
|
||||
Column("ma_hausnummer", sql.Text, nullable=True),
|
||||
Column("ma_plz", sql.Text, nullable=True),
|
||||
Column("ma_ort", sql.Text, nullable=True),
|
||||
Column("ma_plz_postfach", sql.Text, nullable=True),
|
||||
Column("ma_postfach", sql.Text, nullable=True),
|
||||
Column("ma_website", sql.Text, nullable=True),
|
||||
Column("ma_mail", sql.Text, nullable=True),
|
||||
Column("ma_telefonnummer", sql.Text, nullable=True),
|
||||
Column("ma_faxnummer", sql.Text, nullable=True),
|
||||
Column("ma_ersteintrag_datum", SafeDateTime, nullable=True),
|
||||
Column("ma_aktualisierung_datum", SafeDateTime, nullable=True),
|
||||
Column("ma_aktualisierung_nutzer", sql.Text, nullable=True),
|
||||
Column("ma_sollprozess", sql.Text, nullable=True),
|
||||
Column("ma_auslaendische_mitarbeiter", sql.Text, nullable=True),
|
||||
Column("ma_quelle_information", sql.Text, nullable=True),
|
||||
Column("ma_bemerkung", sql.Text, nullable=True),
|
||||
Column("ma_kontakt", sql.Boolean, nullable=True),
|
||||
Column("ma_schlagworte", sql.Text, nullable=True),
|
||||
Column("ma_archiviert", sql.Boolean, nullable=True, default=False),
|
||||
)
|
||||
|
||||
|
||||
ext_crm_master_schema: t.PolarsSchema = {
|
||||
"ma_id": pl.UInt64,
|
||||
"wce_id": pl.UInt64,
|
||||
"ma_unternehmensname": pl.String,
|
||||
"ma_branche": pl.String,
|
||||
"ma_strasse": pl.String,
|
||||
"ma_hausnummer": pl.String,
|
||||
"ma_plz": pl.String,
|
||||
"ma_ort": pl.String,
|
||||
"ma_plz_postfach": pl.String,
|
||||
"ma_postfach": pl.String,
|
||||
"ma_website": pl.String,
|
||||
"ma_mail": pl.String,
|
||||
"ma_telefonnummer": pl.String,
|
||||
"ma_faxnummer": pl.String,
|
||||
"ma_ersteintrag_datum": pl.Datetime,
|
||||
"ma_aktualisierung_datum": pl.Datetime,
|
||||
"ma_aktualisierung_nutzer": pl.String,
|
||||
"ma_sollprozess": pl.String,
|
||||
"ma_auslaendische_mitarbeiter": pl.String,
|
||||
"ma_quelle_information": pl.String,
|
||||
"ma_bemerkung": pl.String,
|
||||
"ma_kontakt": pl.Boolean,
|
||||
"ma_schlagworte": pl.String,
|
||||
"ma_archiviert": pl.Boolean,
|
||||
}
|
||||
|
||||
|
||||
def get_ext_crm_master(
|
||||
db_path: Path | None,
|
||||
) -> pl.DataFrame:
|
||||
if db_path is None:
|
||||
ENV_PTH = os.environ.get("DOPT_DB_CRM", None)
|
||||
if ENV_PTH is None:
|
||||
raise ValueError("No database path provided or found as ENV var.")
|
||||
db_path = Path(ENV_PTH)
|
||||
|
||||
if not db_path.exists():
|
||||
raise FileNotFoundError(f"Database not found under >{db_path}<")
|
||||
|
||||
engine = sql.create_engine(f"sqlite:///{db_path}")
|
||||
stmt = sql.select(ext_crm_master)
|
||||
return pl.read_database(stmt, engine, schema_overrides=ext_crm_master_schema)
|
||||
|
||||
|
||||
df_crm_master = get_ext_crm_master(None)
|
||||
|
||||
ext_crm_nutzer: sql.Table = Table(
|
||||
"Nutzer",
|
||||
md_crm,
|
||||
Column("wce_id", sql.Integer, nullable=False, unique=True),
|
||||
Column("wce_name", sql.Text, nullable=True),
|
||||
Column("wce_vorname", sql.Text, nullable=True),
|
||||
Column("wce_kuerzel", sql.Text, nullable=True),
|
||||
Column("wce_passwort", sql.Text, nullable=True),
|
||||
Column("wce_angelegt_am", sql.DateTime, nullable=True),
|
||||
Column("wce_rolle", sql.Text, nullable=True),
|
||||
Column("wce_angelegt_von", sql.Text, nullable=True),
|
||||
Column("wce_aktiv", sql.Boolean, nullable=True),
|
||||
Column("wce_letzter_login", sql.DateTime, nullable=True),
|
||||
)
|
||||
|
||||
ext_crm_nutzer_schema: t.PolarsSchema = {
|
||||
"wce_id": pl.UInt64,
|
||||
"wce_name": pl.String,
|
||||
"wce_vorname": pl.String,
|
||||
"wce_kuerzel": pl.String,
|
||||
"wce_passwort": pl.String,
|
||||
"wce_angelegt_am": pl.Datetime,
|
||||
"wce_rolle": pl.String,
|
||||
"wce_angelegt_von": pl.String,
|
||||
"wce_aktiv": pl.Boolean,
|
||||
"wce_letzter_login": pl.Datetime,
|
||||
}
|
||||
15
src/wce_crm/env.py
Normal file
15
src/wce_crm/env.py
Normal file
@ -0,0 +1,15 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT = Path(__file__).parents[2]
|
||||
DB_PATH = PROJECT_ROOT / "data/db"
|
||||
|
||||
DB_KONTAKTLISTE = DB_PATH / "wce_kontaktliste.db"
|
||||
assert DB_KONTAKTLISTE.exists()
|
||||
DB_CRM = DB_PATH / "wce_crm.db"
|
||||
assert DB_CRM.exists()
|
||||
|
||||
|
||||
def setup():
|
||||
os.environ["DOPT_DB_KONTAKTLISTE"] = str(DB_KONTAKTLISTE)
|
||||
os.environ["DOPT_DB_CRM"] = str(DB_CRM)
|
||||
2
src/wce_crm/env_vars.txt
Normal file
2
src/wce_crm/env_vars.txt
Normal file
@ -0,0 +1,2 @@
|
||||
DOPT_DB_KONTAKTLISTE: Pfad zur Datenbank der Kontaktliste, falls nicht direkt übergeben (Prototypenphase)
|
||||
DOPT_DB_CRM: Pfad zur CRM-Datenbank, falls nicht direkt übergeben (Prototypenphase)
|
||||
9
src/wce_crm/types.py
Normal file
9
src/wce_crm/types.py
Normal file
@ -0,0 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, TypeAlias
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import polars as pl
|
||||
|
||||
|
||||
PolarsSchema: TypeAlias = dict[str, type["pl.DataType"]]
|
||||
Loading…
x
Reference in New Issue
Block a user