From 6575cf405e8cb22c141a5a1ed35de9b885ca8949 Mon Sep 17 00:00:00 2001 From: ddc <34492089+ddc@users.noreply.github.com> Date: Sun, 1 Mar 2026 14:43:34 -0300 Subject: [PATCH 1/5] v3.0.12 --- LICENSE | 2 +- README.md | 22 ++++++++++---------- pyproject.toml | 6 +++--- uv.lock | 56 +++++++++++++++++++++++++------------------------- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/LICENSE b/LICENSE index badf491..56823d1 100755 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024-present ddc +Copyright (C) 2024 DDC Softwares Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index f3f4257..8ce7a56 100755 --- a/README.md +++ b/README.md @@ -5,25 +5,25 @@

- Donate + Donate Sponsor
- License: MIT - PyPI Downloads PyPi + PyPI Downloads + License: MIT
Python - uv - Ruff + uv + Ruff
- issues - codecov - Quality Gate Status - CI/CD Pipeline - Build Status + issues + codecov + Quality Gate Status + CI/CD Pipeline + Build Status

-

A Python library for database connections and ORM queries with support for multiple database engines including SQLite, PostgreSQL, MySQL, MariaDB, MSSQL, Oracle, and MongoDB

+

A Python library for database connections and ORM queries with support for multiple database engines.
Includes SQLite, PostgreSQL, MySQL/MariaDB, MSSQL, Oracle, and MongoDB

# Table of Contents diff --git a/pyproject.toml b/pyproject.toml index 467f79b..a5848f8 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ packages = ["ddcDatabases"] [project] name = "ddcDatabases" -version = "3.0.11" +version = "3.0.12" description = "Simplified database ORM connections with support for multiple database engines" urls.Repository = "https://github.com/ddc/ddcDatabases" urls.Homepage = "https://pypi.org/project/ddcDatabases" @@ -70,9 +70,9 @@ mariadb = ["ddcDatabases[mysql]"] [dependency-groups] dev = [ "coverage>=7.13.4", - "poethepoet>=0.42.0", + "poethepoet>=0.42.1", "pytest-asyncio>=1.3.0", - "ruff>=0.15.2", + "ruff>=0.15.4", "testcontainers[postgres,mysql,mssql,mongodb,oracle]>=4.14.1", ] diff --git a/uv.lock b/uv.lock index c1e3a36..a69fb4a 100644 --- a/uv.lock +++ b/uv.lock @@ -476,7 +476,7 @@ wheels = [ [[package]] name = "ddcdatabases" -version = "3.0.11" +version = "3.0.12" source = { editable = "." } dependencies = [ { name = "pydantic-settings" }, @@ -540,9 +540,9 @@ provides-extras = ["mongodb", "oracle", "mssql", "mysql", "postgres", "pgsql", " [package.metadata.requires-dev] dev = [ { name = "coverage", specifier = ">=7.13.4" }, - { name = "poethepoet", specifier = ">=0.42.0" }, + { name = "poethepoet", specifier = ">=0.42.1" }, { name = "pytest-asyncio", specifier = ">=1.3.0" }, - { name = "ruff", specifier = ">=0.15.2" }, + { name = "ruff", specifier = ">=0.15.4" }, { name = "testcontainers", extras = ["postgres", "mysql", "mssql", "mongodb", "oracle"], specifier = ">=4.14.1" }, ] @@ -751,16 +751,16 @@ wheels = [ [[package]] name = "poethepoet" -version = "0.42.0" +version = "0.42.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pastel" }, { name = "pyyaml" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4a/9a/4e81fafef2ba94e5c974b4701343d1f053a27575ab5133cbd264348925dd/poethepoet-0.42.0.tar.gz", hash = "sha256:c9a2828259e585e9ed152857602130ff339f7b1638879b80d4a23f25588be4f8", size = 91278, upload-time = "2026-02-22T14:24:50.967Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/9b/e717572686bbf23e17483389c1bf3a381ca2427c84c7e0af0cdc0f23fccc/poethepoet-0.42.1.tar.gz", hash = "sha256:205747e276062c2aaba8afd8a98838f8a3a0237b7ab94715fab8d82718aac14f", size = 93209, upload-time = "2026-02-26T22:57:50.883Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/3e/58041b7e4d49b69e859dc81c35e221cf02d91ed4dbb5a2f6cc4698a29f44/poethepoet-0.42.0-py3-none-any.whl", hash = "sha256:e43cc20d458ee5bfccaa4572bc5783bcb93991a7d2fcf8dadc9c43f1ebc9b277", size = 118091, upload-time = "2026-02-22T14:24:49.53Z" }, + { url = "https://files.pythonhosted.org/packages/c8/68/75fa0a5ef39718ea6ba7ab6a3d031fa93640e57585580cec85539540bb65/poethepoet-0.42.1-py3-none-any.whl", hash = "sha256:d8d1345a5ca521be9255e7c13bc2c4c8698ed5e5ac5e9e94890d239fcd423d0a", size = 119967, upload-time = "2026-02-26T22:57:49.467Z" }, ] [[package]] @@ -1232,11 +1232,11 @@ wheels = [ [[package]] name = "python-dotenv" -version = "1.2.1" +version = "1.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, ] [[package]] @@ -1342,27 +1342,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.2" +version = "0.15.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/06/04/eab13a954e763b0606f460443fcbf6bb5a0faf06890ea3754ff16523dce5/ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342", size = 4558148, upload-time = "2026-02-19T22:32:20.271Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/31/d6e536cdebb6568ae75a7f00e4b4819ae0ad2640c3604c305a0428680b0c/ruff-0.15.4.tar.gz", hash = "sha256:3412195319e42d634470cc97aa9803d07e9d5c9223b99bcb1518f0c725f26ae1", size = 4569550, upload-time = "2026-02-26T20:04:14.959Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/70/3a4dc6d09b13cb3e695f28307e5d889b2e1a66b7af9c5e257e796695b0e6/ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d", size = 10430565, upload-time = "2026-02-19T22:32:41.824Z" }, - { url = "https://files.pythonhosted.org/packages/71/0b/bb8457b56185ece1305c666dc895832946d24055be90692381c31d57466d/ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e", size = 10820354, upload-time = "2026-02-19T22:32:07.366Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c1/e0532d7f9c9e0b14c46f61b14afd563298b8b83f337b6789ddd987e46121/ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87", size = 10170767, upload-time = "2026-02-19T22:32:13.188Z" }, - { url = "https://files.pythonhosted.org/packages/47/e8/da1aa341d3af017a21c7a62fb5ec31d4e7ad0a93ab80e3a508316efbcb23/ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9", size = 10529591, upload-time = "2026-02-19T22:32:02.547Z" }, - { url = "https://files.pythonhosted.org/packages/93/74/184fbf38e9f3510231fbc5e437e808f0b48c42d1df9434b208821efcd8d6/ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80", size = 10260771, upload-time = "2026-02-19T22:32:36.938Z" }, - { url = "https://files.pythonhosted.org/packages/05/ac/605c20b8e059a0bc4b42360414baa4892ff278cec1c91fff4be0dceedefd/ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f", size = 11045791, upload-time = "2026-02-19T22:32:31.642Z" }, - { url = "https://files.pythonhosted.org/packages/fd/52/db6e419908f45a894924d410ac77d64bdd98ff86901d833364251bd08e22/ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77", size = 11879271, upload-time = "2026-02-19T22:32:29.305Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d8/7992b18f2008bdc9231d0f10b16df7dda964dbf639e2b8b4c1b4e91b83af/ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea", size = 11303707, upload-time = "2026-02-19T22:32:22.492Z" }, - { url = "https://files.pythonhosted.org/packages/d7/02/849b46184bcfdd4b64cde61752cc9a146c54759ed036edd11857e9b8443b/ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a", size = 11149151, upload-time = "2026-02-19T22:32:44.234Z" }, - { url = "https://files.pythonhosted.org/packages/70/04/f5284e388bab60d1d3b99614a5a9aeb03e0f333847e2429bebd2aaa1feec/ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956", size = 11091132, upload-time = "2026-02-19T22:32:24.691Z" }, - { url = "https://files.pythonhosted.org/packages/fa/ae/88d844a21110e14d92cf73d57363fab59b727ebeabe78009b9ccb23500af/ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4", size = 10504717, upload-time = "2026-02-19T22:32:26.75Z" }, - { url = "https://files.pythonhosted.org/packages/64/27/867076a6ada7f2b9c8292884ab44d08fd2ba71bd2b5364d4136f3cd537e1/ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de", size = 10263122, upload-time = "2026-02-19T22:32:10.036Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ef/faf9321d550f8ebf0c6373696e70d1758e20ccdc3951ad7af00c0956be7c/ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c", size = 10735295, upload-time = "2026-02-19T22:32:39.227Z" }, - { url = "https://files.pythonhosted.org/packages/2f/55/e8089fec62e050ba84d71b70e7834b97709ca9b7aba10c1a0b196e493f97/ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8", size = 11241641, upload-time = "2026-02-19T22:32:34.617Z" }, - { url = "https://files.pythonhosted.org/packages/23/01/1c30526460f4d23222d0fabd5888868262fd0e2b71a00570ca26483cd993/ruff-0.15.2-py3-none-win32.whl", hash = "sha256:fd5ff9e5f519a7e1bd99cbe8daa324010a74f5e2ebc97c6242c08f26f3714f6f", size = 10507885, upload-time = "2026-02-19T22:32:15.635Z" }, - { url = "https://files.pythonhosted.org/packages/5c/10/3d18e3bbdf8fc50bbb4ac3cc45970aa5a9753c5cb51bf9ed9a3cd8b79fa3/ruff-0.15.2-py3-none-win_amd64.whl", hash = "sha256:d20014e3dfa400f3ff84830dfb5755ece2de45ab62ecea4af6b7262d0fb4f7c5", size = 11623725, upload-time = "2026-02-19T22:32:04.947Z" }, - { url = "https://files.pythonhosted.org/packages/6d/78/097c0798b1dab9f8affe73da9642bb4500e098cb27fd8dc9724816ac747b/ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e", size = 10941649, upload-time = "2026-02-19T22:32:18.108Z" }, + { url = "https://files.pythonhosted.org/packages/f2/82/c11a03cfec3a4d26a0ea1e571f0f44be5993b923f905eeddfc397c13d360/ruff-0.15.4-py3-none-linux_armv6l.whl", hash = "sha256:a1810931c41606c686bae8b5b9a8072adac2f611bb433c0ba476acba17a332e0", size = 10453333, upload-time = "2026-02-26T20:04:20.093Z" }, + { url = "https://files.pythonhosted.org/packages/ce/5d/6a1f271f6e31dffb31855996493641edc3eef8077b883eaf007a2f1c2976/ruff-0.15.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a1632c66672b8b4d3e1d1782859e98d6e0b4e70829530666644286600a33992", size = 10853356, upload-time = "2026-02-26T20:04:05.808Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d8/0fab9f8842b83b1a9c2bf81b85063f65e93fb512e60effa95b0be49bfc54/ruff-0.15.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4386ba2cd6c0f4ff75252845906acc7c7c8e1ac567b7bc3d373686ac8c222ba", size = 10187434, upload-time = "2026-02-26T20:03:54.656Z" }, + { url = "https://files.pythonhosted.org/packages/85/cc/cc220fd9394eff5db8d94dec199eec56dd6c9f3651d8869d024867a91030/ruff-0.15.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2496488bdfd3732747558b6f95ae427ff066d1fcd054daf75f5a50674411e75", size = 10535456, upload-time = "2026-02-26T20:03:52.738Z" }, + { url = "https://files.pythonhosted.org/packages/fa/0f/bced38fa5cf24373ec767713c8e4cadc90247f3863605fb030e597878661/ruff-0.15.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f1c4893841ff2d54cbda1b2860fa3260173df5ddd7b95d370186f8a5e66a4ac", size = 10287772, upload-time = "2026-02-26T20:04:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/2b/90/58a1802d84fed15f8f281925b21ab3cecd813bde52a8ca033a4de8ab0e7a/ruff-0.15.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:820b8766bd65503b6c30aaa6331e8ef3a6e564f7999c844e9a547c40179e440a", size = 11049051, upload-time = "2026-02-26T20:04:03.53Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ac/b7ad36703c35f3866584564dc15f12f91cb1a26a897dc2fd13d7cb3ae1af/ruff-0.15.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9fb74bab47139c1751f900f857fa503987253c3ef89129b24ed375e72873e85", size = 11890494, upload-time = "2026-02-26T20:04:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/93/3d/3eb2f47a39a8b0da99faf9c54d3eb24720add1e886a5309d4d1be73a6380/ruff-0.15.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f80c98765949c518142b3a50a5db89343aa90f2c2bf7799de9986498ae6176db", size = 11326221, upload-time = "2026-02-26T20:04:12.84Z" }, + { url = "https://files.pythonhosted.org/packages/ff/90/bf134f4c1e5243e62690e09d63c55df948a74084c8ac3e48a88468314da6/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451a2e224151729b3b6c9ffb36aed9091b2996fe4bdbd11f47e27d8f2e8888ec", size = 11168459, upload-time = "2026-02-26T20:04:00.969Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e5/a64d27688789b06b5d55162aafc32059bb8c989c61a5139a36e1368285eb/ruff-0.15.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8f157f2e583c513c4f5f896163a93198297371f34c04220daf40d133fdd4f7f", size = 11104366, upload-time = "2026-02-26T20:03:48.099Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f6/32d1dcb66a2559763fc3027bdd65836cad9eb09d90f2ed6a63d8e9252b02/ruff-0.15.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:917cc68503357021f541e69b35361c99387cdbbf99bd0ea4aa6f28ca99ff5338", size = 10510887, upload-time = "2026-02-26T20:03:45.771Z" }, + { url = "https://files.pythonhosted.org/packages/ff/92/22d1ced50971c5b6433aed166fcef8c9343f567a94cf2b9d9089f6aa80fe/ruff-0.15.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e9737c8161da79fd7cfec19f1e35620375bd8b2a50c3e77fa3d2c16f574105cc", size = 10285939, upload-time = "2026-02-26T20:04:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f4/7c20aec3143837641a02509a4668fb146a642fd1211846634edc17eb5563/ruff-0.15.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:291258c917539e18f6ba40482fe31d6f5ac023994ee11d7bdafd716f2aab8a68", size = 10765471, upload-time = "2026-02-26T20:03:58.924Z" }, + { url = "https://files.pythonhosted.org/packages/d0/09/6d2f7586f09a16120aebdff8f64d962d7c4348313c77ebb29c566cefc357/ruff-0.15.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3f83c45911da6f2cd5936c436cf86b9f09f09165f033a99dcf7477e34041cbc3", size = 11263382, upload-time = "2026-02-26T20:04:24.424Z" }, + { url = "https://files.pythonhosted.org/packages/1b/fa/2ef715a1cd329ef47c1a050e10dee91a9054b7ce2fcfdd6a06d139afb7ec/ruff-0.15.4-py3-none-win32.whl", hash = "sha256:65594a2d557d4ee9f02834fcdf0a28daa8b3b9f6cb2cb93846025a36db47ef22", size = 10506664, upload-time = "2026-02-26T20:03:50.56Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a8/c688ef7e29983976820d18710f955751d9f4d4eb69df658af3d006e2ba3e/ruff-0.15.4-py3-none-win_amd64.whl", hash = "sha256:04196ad44f0df220c2ece5b0e959c2f37c777375ec744397d21d15b50a75264f", size = 11651048, upload-time = "2026-02-26T20:04:17.191Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" }, ] [[package]] From b250377e4820485f3c991575c7c9803e49568f04 Mon Sep 17 00:00:00 2001 From: ddc <34492089+ddc@users.noreply.github.com> Date: Tue, 3 Mar 2026 13:01:09 -0300 Subject: [PATCH 2/5] v3.0.12 --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ce7a56..d685d4d 100755 --- a/README.md +++ b/README.md @@ -5,9 +5,11 @@

- Donate Sponsor
+ Ko-fi + Donate +
PyPi PyPI Downloads License: MIT From d0a7b2447585ba00a376222ed5a39a76c21a834c Mon Sep 17 00:00:00 2001 From: ddc <34492089+ddc@users.noreply.github.com> Date: Mon, 16 Mar 2026 23:43:23 -0300 Subject: [PATCH 3/5] v4.0.0 --- .github/PULL_REQUEST_TEMPLATE | 10 +- {ddcDatabases => ddcdatabases}/.env.example | 2 +- {ddcDatabases => ddcdatabases}/__init__.py | 0 .../core/__init__.py | 0 {ddcDatabases => ddcdatabases}/core/base.py | 1 - .../core/configs.py | 0 .../core/constants.py | 2 +- .../core/exceptions.py | 0 .../core/operations.py | 1 - .../core/persistent.py | 1 - {ddcDatabases => ddcdatabases}/core/retry.py | 3 +- .../core/settings.py | 0 {ddcDatabases => ddcdatabases}/mongodb.py | 0 {ddcDatabases => ddcdatabases}/mssql.py | 0 {ddcDatabases => ddcdatabases}/mysql.py | 0 {ddcDatabases => ddcdatabases}/oracle.py | 0 {ddcDatabases => ddcdatabases}/postgresql.py | 0 {ddcDatabases => ddcdatabases}/sqlite.py | 0 pyproject.toml | 21 +- .../core/test_persistent_multiple.py | 2 +- tests/integration/mariadb/test_mariadb.py | 12 +- .../mariadb/test_mariadb_persistent.py | 4 +- tests/integration/mariadb/test_mariadb_ssl.py | 8 +- tests/integration/mongodb/test_mongodb.py | 14 +- .../mongodb/test_mongodb_persistent.py | 2 +- tests/integration/mongodb/test_mongodb_tls.py | 12 +- tests/integration/mssql/test_mssql.py | 8 +- .../mssql/test_mssql_persistent.py | 2 +- tests/integration/mssql/test_mssql_ssl.py | 16 +- tests/integration/mysql/test_mysql.py | 10 +- .../mysql/test_mysql_persistent.py | 2 +- tests/integration/mysql/test_mysql_ssl.py | 20 +- tests/integration/oracle/test_oracle.py | 8 +- .../oracle/test_oracle_persistent.py | 2 +- tests/integration/oracle/test_oracle_ssl.py | 12 +- .../integration/postgresql/test_postgresql.py | 12 +- .../postgresql/test_postgresql_persistent.py | 2 +- .../postgresql/test_postgresql_ssl.py | 24 +- tests/integration/sqlite/test_sqlite.py | 8 +- tests/smoke_test.py | 4 +- tests/unit/conftest.py | 8 +- tests/unit/core/test_async_functionality.py | 14 +- tests/unit/core/test_db_utils.py | 24 +- tests/unit/core/test_exceptions.py | 2 +- tests/unit/core/test_init_module.py | 56 +-- tests/unit/core/test_persistent.py | 36 +- tests/unit/core/test_retry_logic.py | 22 +- tests/unit/core/test_settings.py | 14 +- tests/unit/core/test_ssl_configs.py | 8 +- tests/unit/mariadb/test_mariadb_persistent.py | 4 +- tests/unit/mariadb/test_mariadb_ssl.py | 14 +- tests/unit/mongodb/test_mongodb.py | 98 ++-- tests/unit/mongodb/test_mongodb_persistent.py | 2 +- tests/unit/mongodb/test_mongodb_tls.py | 16 +- tests/unit/mssql/test_mssql.py | 40 +- tests/unit/mssql/test_mssql_persistent.py | 2 +- tests/unit/mssql/test_mssql_ssl.py | 40 +- tests/unit/mysql/test_mysql.py | 32 +- tests/unit/mysql/test_mysql_persistent.py | 4 +- tests/unit/mysql/test_mysql_ssl.py | 22 +- tests/unit/oracle/test_oracle.py | 44 +- tests/unit/oracle/test_oracle_persistent.py | 2 +- tests/unit/oracle/test_oracle_ssl.py | 12 +- tests/unit/postgresql/test_postgresql.py | 80 +-- .../postgresql/test_postgresql_persistent.py | 4 +- tests/unit/postgresql/test_postgresql_ssl.py | 74 +-- tests/unit/sqlite/test_sqlite.py | 14 +- uv.lock | 472 ++++++++++-------- 68 files changed, 701 insertions(+), 684 deletions(-) rename {ddcDatabases => ddcdatabases}/.env.example (99%) rename {ddcDatabases => ddcdatabases}/__init__.py (100%) rename {ddcDatabases => ddcdatabases}/core/__init__.py (100%) rename {ddcDatabases => ddcdatabases}/core/base.py (99%) rename {ddcDatabases => ddcdatabases}/core/configs.py (100%) rename {ddcDatabases => ddcdatabases}/core/constants.py (98%) rename {ddcDatabases => ddcdatabases}/core/exceptions.py (100%) rename {ddcDatabases => ddcdatabases}/core/operations.py (99%) rename {ddcDatabases => ddcdatabases}/core/persistent.py (99%) rename {ddcDatabases => ddcdatabases}/core/retry.py (98%) rename {ddcDatabases => ddcdatabases}/core/settings.py (100%) rename {ddcDatabases => ddcdatabases}/mongodb.py (100%) rename {ddcDatabases => ddcdatabases}/mssql.py (100%) rename {ddcDatabases => ddcdatabases}/mysql.py (100%) rename {ddcDatabases => ddcdatabases}/oracle.py (100%) rename {ddcDatabases => ddcdatabases}/postgresql.py (100%) rename {ddcDatabases => ddcdatabases}/sqlite.py (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE index e31472a..eba1f84 100755 --- a/.github/PULL_REQUEST_TEMPLATE +++ b/.github/PULL_REQUEST_TEMPLATE @@ -12,18 +12,10 @@ - [ ] Documentation - [ ] CI/CD or build configuration - [ ] Dependencies update +- [ ] Other ## Testing -- [ ] Unit tests added/updated -- [ ] Integration tests added/updated -- [ ] All existing tests pass - [ ] Manual testing performed -## Checklist -- [ ] Code follows the project's style and conventions -- [ ] Documentation updated (if applicable) -- [ ] No new warnings or linter errors introduced -- [ ] I have considered how this change may affect other services - ## Reviewer - [ ] I understand that by approving this PR, I share responsibility for these changes diff --git a/ddcDatabases/.env.example b/ddcdatabases/.env.example similarity index 99% rename from ddcDatabases/.env.example rename to ddcdatabases/.env.example index 32b55f1..c646397 100644 --- a/ddcDatabases/.env.example +++ b/ddcdatabases/.env.example @@ -1,4 +1,4 @@ -# ddcDatabases Environment Variables Example +# ddcdatabases Environment Variables Example # Copy this file to .env and adjust values as needed # SQLite Database Settings diff --git a/ddcDatabases/__init__.py b/ddcdatabases/__init__.py similarity index 100% rename from ddcDatabases/__init__.py rename to ddcdatabases/__init__.py diff --git a/ddcDatabases/core/__init__.py b/ddcdatabases/core/__init__.py similarity index 100% rename from ddcDatabases/core/__init__.py rename to ddcdatabases/core/__init__.py diff --git a/ddcDatabases/core/base.py b/ddcdatabases/core/base.py similarity index 99% rename from ddcDatabases/core/base.py rename to ddcdatabases/core/base.py index 7bf712e..7fc2be3 100644 --- a/ddcDatabases/core/base.py +++ b/ddcdatabases/core/base.py @@ -1,5 +1,4 @@ from __future__ import annotations - import logging import sqlalchemy as sa from .configs import BaseOperationRetryConfig, BaseRetryConfig diff --git a/ddcDatabases/core/configs.py b/ddcdatabases/core/configs.py similarity index 100% rename from ddcDatabases/core/configs.py rename to ddcdatabases/core/configs.py diff --git a/ddcDatabases/core/constants.py b/ddcdatabases/core/constants.py similarity index 98% rename from ddcDatabases/core/constants.py rename to ddcdatabases/core/constants.py index ad54fb8..3ebc0c6 100644 --- a/ddcDatabases/core/constants.py +++ b/ddcdatabases/core/constants.py @@ -69,7 +69,7 @@ class SettingsMessages: HOST_DESCRIPTION = "Database host" PORT_DESCRIPTION = "Database port" USERNAME_DESCRIPTION = "Database username" - PASSWORD_DESCRIPTION = "Database password" + PASSWORD_DESCRIPTION = "Database password" # noqa: S105 NAME_DESCRIPTION = "Database name" SCHEMA_DESCRIPTION = "Database schema (comma-separated for multiple schemas, e.g. 'gw2,public')" ASYNC_DATABASE_DRIVER_DESCRIPTION = "Async database driver" diff --git a/ddcDatabases/core/exceptions.py b/ddcdatabases/core/exceptions.py similarity index 100% rename from ddcDatabases/core/exceptions.py rename to ddcdatabases/core/exceptions.py diff --git a/ddcDatabases/core/operations.py b/ddcdatabases/core/operations.py similarity index 99% rename from ddcDatabases/core/operations.py rename to ddcdatabases/core/operations.py index 8572dea..fe5f3ef 100644 --- a/ddcDatabases/core/operations.py +++ b/ddcdatabases/core/operations.py @@ -1,5 +1,4 @@ from __future__ import annotations - import logging import sqlalchemy as sa from .configs import BaseOperationRetryConfig diff --git a/ddcDatabases/core/persistent.py b/ddcdatabases/core/persistent.py similarity index 99% rename from ddcDatabases/core/persistent.py rename to ddcdatabases/core/persistent.py index 3022ab0..aafbbe1 100644 --- a/ddcDatabases/core/persistent.py +++ b/ddcdatabases/core/persistent.py @@ -7,7 +7,6 @@ """ from __future__ import annotations - import asyncio import logging import ssl as _ssl_module diff --git a/ddcDatabases/core/retry.py b/ddcdatabases/core/retry.py similarity index 98% rename from ddcDatabases/core/retry.py rename to ddcdatabases/core/retry.py index 9a8b0bc..7382176 100644 --- a/ddcDatabases/core/retry.py +++ b/ddcdatabases/core/retry.py @@ -1,5 +1,4 @@ from __future__ import annotations - import asyncio import logging import random @@ -57,7 +56,7 @@ def _calculate_retry_delay(attempt: int, config: BaseRetryConfig) -> float: # Add jitter (randomize +/- jitter%) jitter = getattr(config, "jitter", 0.0) or 0.0 jitter_range = capped_delay * jitter - jitter_offset = random.uniform(-jitter_range, jitter_range) + jitter_offset = random.uniform(-jitter_range, jitter_range) # noqa: S311 return max(0, capped_delay + jitter_offset) diff --git a/ddcDatabases/core/settings.py b/ddcdatabases/core/settings.py similarity index 100% rename from ddcDatabases/core/settings.py rename to ddcdatabases/core/settings.py diff --git a/ddcDatabases/mongodb.py b/ddcdatabases/mongodb.py similarity index 100% rename from ddcDatabases/mongodb.py rename to ddcdatabases/mongodb.py diff --git a/ddcDatabases/mssql.py b/ddcdatabases/mssql.py similarity index 100% rename from ddcDatabases/mssql.py rename to ddcdatabases/mssql.py diff --git a/ddcDatabases/mysql.py b/ddcdatabases/mysql.py similarity index 100% rename from ddcDatabases/mysql.py rename to ddcdatabases/mysql.py diff --git a/ddcDatabases/oracle.py b/ddcdatabases/oracle.py similarity index 100% rename from ddcDatabases/oracle.py rename to ddcdatabases/oracle.py diff --git a/ddcDatabases/postgresql.py b/ddcdatabases/postgresql.py similarity index 100% rename from ddcDatabases/postgresql.py rename to ddcdatabases/postgresql.py diff --git a/ddcDatabases/sqlite.py b/ddcdatabases/sqlite.py similarity index 100% rename from ddcDatabases/sqlite.py rename to ddcdatabases/sqlite.py diff --git a/pyproject.toml b/pyproject.toml index a5848f8..52713b9 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,14 +6,14 @@ build-backend = "hatchling.build" allow-direct-references = true [tool.hatch.build] -include = ["ddcDatabases/**/*"] +include = ["ddcdatabases/**/*"] [tool.hatch.build.targets.wheel] -packages = ["ddcDatabases"] +packages = ["ddcdatabases"] [project] name = "ddcDatabases" -version = "3.0.12" +version = "4.0.0" description = "Simplified database ORM connections with support for multiple database engines" urls.Repository = "https://github.com/ddc/ddcDatabases" urls.Homepage = "https://pypi.org/project/ddcDatabases" @@ -55,7 +55,7 @@ classifiers = [ requires-python = ">=3.10" dependencies = [ "pydantic-settings>=2.11.0", - "sqlalchemy[asyncio]>=2.0.47", + "sqlalchemy[asyncio]>=2.0.48", ] [project.optional-dependencies] @@ -64,15 +64,15 @@ oracle = ["oracledb>=3.4.2"] mssql = ["pyodbc>=5.3.0", "aioodbc>=0.5.0"] mysql = ["mysqlclient>=2.2.8", "aiomysql>=0.3.2"] postgres = ["psycopg[binary]>=3.3.3", "asyncpg>=0.31.0"] -pgsql = ["ddcDatabases[postgres]"] -mariadb = ["ddcDatabases[mysql]"] +pgsql = ["ddcdatabases[postgres]"] +mariadb = ["ddcdatabases[mysql]"] [dependency-groups] dev = [ "coverage>=7.13.4", "poethepoet>=0.42.1", "pytest-asyncio>=1.3.0", - "ruff>=0.15.4", + "ruff>=0.15.6", "testcontainers[postgres,mysql,mssql,mongodb,oracle]>=4.14.1", ] @@ -124,7 +124,7 @@ line-length = 120 target-version = "py310" [tool.ruff.lint] -select = ["E", "W", "F", "I", "B", "C4", "UP"] +select = ["E", "W", "F", "I", "B", "C4", "UP", "S", "SLF"] ignore = ["E501", "E402", "UP046", "UP047"] [tool.ruff.lint.per-file-ignores] @@ -132,7 +132,6 @@ ignore = ["E501", "E402", "UP046", "UP047"] "tests/**/*.py" = ["S101", "S105", "S106", "S311", "SLF001", "F841"] [tool.ruff.lint.isort] -known-first-party = ["ddcDatabases"] -force-sort-within-sections = false -from-first = false +known-first-party = ["ddcdatabases"] +no-lines-before = ["future", "standard-library", "third-party", "first-party", "local-folder"] no-sections = true diff --git a/tests/integration/core/test_persistent_multiple.py b/tests/integration/core/test_persistent_multiple.py index 2aebed3..6b0e66a 100644 --- a/tests/integration/core/test_persistent_multiple.py +++ b/tests/integration/core/test_persistent_multiple.py @@ -4,7 +4,7 @@ import sqlalchemy as sa # noinspection PyProtectedMember -from ddcDatabases.core.persistent import ( +from ddcdatabases.core.persistent import ( MySQLPersistent, PostgreSQLPersistent, close_all_persistent_connections, diff --git a/tests/integration/mariadb/test_mariadb.py b/tests/integration/mariadb/test_mariadb.py index 4add1be..86df280 100644 --- a/tests/integration/mariadb/test_mariadb.py +++ b/tests/integration/mariadb/test_mariadb.py @@ -10,7 +10,7 @@ class TestMariaDBIntegration: def test_sync_connection(self, mariadb_container): """Test synchronous MariaDB connection and CRUD operations.""" - from ddcDatabases import DBUtils, MySQL + from ddcdatabases import DBUtils, MySQL port = mariadb_container.get_exposed_port(3306) host = mariadb_container.get_container_host_ip() @@ -61,7 +61,7 @@ def test_sync_connection(self, mariadb_container): @pytest.mark.asyncio async def test_async_connection(self, mariadb_container): """Test asynchronous MariaDB connection and CRUD operations.""" - from ddcDatabases import DBUtilsAsync, MySQL + from ddcdatabases import DBUtilsAsync, MySQL port = mariadb_container.get_exposed_port(3306) host = mariadb_container.get_container_host_ip() @@ -104,12 +104,12 @@ async def test_async_connection(self, mariadb_container): def test_pool_size_configuration(self, mariadb_container): """Test MariaDB pool size configuration works with real connection.""" - from ddcDatabases import MySQL + from ddcdatabases import MySQL port = mariadb_container.get_exposed_port(3306) host = mariadb_container.get_container_host_ip() - from ddcDatabases.mysql import MySQLPoolConfig + from ddcdatabases.mysql import MySQLPoolConfig with MySQL( host=host, @@ -124,7 +124,7 @@ def test_pool_size_configuration(self, mariadb_container): def test_fetchvalue(self, mariadb_container): """Test fetchvalue utility method.""" - from ddcDatabases import DBUtils, MySQL + from ddcdatabases import DBUtils, MySQL port = mariadb_container.get_exposed_port(3306) host = mariadb_container.get_container_host_ip() @@ -154,7 +154,7 @@ def test_fetchvalue(self, mariadb_container): def test_mariadb_version(self, mariadb_container): """Test that the container is actually running MariaDB.""" - from ddcDatabases import MySQL + from ddcdatabases import MySQL port = mariadb_container.get_exposed_port(3306) host = mariadb_container.get_container_host_ip() diff --git a/tests/integration/mariadb/test_mariadb_persistent.py b/tests/integration/mariadb/test_mariadb_persistent.py index 4465803..1a6bc79 100644 --- a/tests/integration/mariadb/test_mariadb_persistent.py +++ b/tests/integration/mariadb/test_mariadb_persistent.py @@ -8,8 +8,8 @@ import sqlalchemy as sa # noinspection PyProtectedMember -from ddcDatabases import MariaDBPersistent -from ddcDatabases.core.persistent import close_all_persistent_connections +from ddcdatabases import MariaDBPersistent +from ddcdatabases.core.persistent import close_all_persistent_connections from tests.integration.conftest import Base, IntegrationModel pytestmark = pytest.mark.integration diff --git a/tests/integration/mariadb/test_mariadb_ssl.py b/tests/integration/mariadb/test_mariadb_ssl.py index aba0006..53bd7b9 100644 --- a/tests/integration/mariadb/test_mariadb_ssl.py +++ b/tests/integration/mariadb/test_mariadb_ssl.py @@ -15,7 +15,7 @@ class TestMariaDBSSLIntegration: def test_connection_with_ssl_disabled(self, mariadb_container): """Test MariaDB connection with SSL explicitly disabled.""" - from ddcDatabases import MariaDB, MariaDBSSLConfig + from ddcdatabases import MariaDB, MariaDBSSLConfig port = mariadb_container.get_exposed_port(3306) host = mariadb_container.get_container_host_ip() @@ -33,7 +33,7 @@ def test_connection_with_ssl_disabled(self, mariadb_container): def test_connection_with_ssl_preferred(self, mariadb_container): """Test MariaDB connection with SSL preferred mode.""" - from ddcDatabases import MariaDB, MariaDBSSLConfig + from ddcdatabases import MariaDB, MariaDBSSLConfig port = mariadb_container.get_exposed_port(3306) host = mariadb_container.get_container_host_ip() @@ -51,7 +51,7 @@ def test_connection_with_ssl_preferred(self, mariadb_container): def test_ssl_config_is_accessible(self, mariadb_container): """Test that MariaDB SSL config is accessible.""" - from ddcDatabases import MariaDB, MariaDBSSLConfig + from ddcdatabases import MariaDB, MariaDBSSLConfig port = mariadb_container.get_exposed_port(3306) host = mariadb_container.get_container_host_ip() @@ -74,7 +74,7 @@ def test_ssl_config_is_accessible(self, mariadb_container): def test_ssl_config_immutable(self, mariadb_container): """Test that MariaDB SSL config is immutable.""" - from ddcDatabases import MariaDB, MariaDBSSLConfig + from ddcdatabases import MariaDB, MariaDBSSLConfig port = mariadb_container.get_exposed_port(3306) host = mariadb_container.get_container_host_ip() diff --git a/tests/integration/mongodb/test_mongodb.py b/tests/integration/mongodb/test_mongodb.py index d7aa904..bdf949e 100644 --- a/tests/integration/mongodb/test_mongodb.py +++ b/tests/integration/mongodb/test_mongodb.py @@ -8,12 +8,12 @@ class TestMongoDBIntegration: def test_connection_and_query(self, mongodb_container): """Test MongoDB connection and basic query operations.""" - from ddcDatabases import MongoDB + from ddcdatabases import MongoDB port = mongodb_container.get_exposed_port(27017) host = f"{mongodb_container.get_container_host_ip()}:{port}" - from ddcDatabases.mongodb import MongoDBQueryConfig + from ddcdatabases.mongodb import MongoDBQueryConfig mongo = MongoDB( host=host, @@ -46,7 +46,7 @@ def test_connection_and_query(self, mongodb_container): def test_query_with_sort(self, mongodb_container): """Test MongoDB query with sort parameters.""" - from ddcDatabases import MongoDB + from ddcdatabases import MongoDB port = mongodb_container.get_exposed_port(27017) host = f"{mongodb_container.get_container_host_ip()}:{port}" @@ -86,12 +86,12 @@ def test_query_with_sort(self, mongodb_container): def test_batch_size_and_limit(self, mongodb_container): """Test MongoDB batch_size and limit parameters.""" - from ddcDatabases import MongoDB + from ddcdatabases import MongoDB port = mongodb_container.get_exposed_port(27017) host = f"{mongodb_container.get_container_host_ip()}:{port}" - from ddcDatabases.mongodb import MongoDBQueryConfig + from ddcdatabases.mongodb import MongoDBQueryConfig mongo = MongoDB( host=host, @@ -116,7 +116,7 @@ def test_batch_size_and_limit(self, mongodb_container): def test_empty_query_returns_all(self, mongodb_container): """Test MongoDB with empty query returns all documents.""" - from ddcDatabases import MongoDB + from ddcdatabases import MongoDB port = mongodb_container.get_exposed_port(27017) host = f"{mongodb_container.get_container_host_ip()}:{port}" @@ -149,7 +149,7 @@ def test_empty_query_returns_all(self, mongodb_container): def test_context_manager_cleanup(self, mongodb_container): """Test MongoDB context manager properly cleans up.""" - from ddcDatabases import MongoDB + from ddcdatabases import MongoDB port = mongodb_container.get_exposed_port(27017) host = f"{mongodb_container.get_container_host_ip()}:{port}" diff --git a/tests/integration/mongodb/test_mongodb_persistent.py b/tests/integration/mongodb/test_mongodb_persistent.py index 35b59a5..29be8f8 100644 --- a/tests/integration/mongodb/test_mongodb_persistent.py +++ b/tests/integration/mongodb/test_mongodb_persistent.py @@ -3,7 +3,7 @@ import pytest # noinspection PyProtectedMember -from ddcDatabases.core.persistent import ( +from ddcdatabases.core.persistent import ( MongoDBPersistent, close_all_persistent_connections, ) diff --git a/tests/integration/mongodb/test_mongodb_tls.py b/tests/integration/mongodb/test_mongodb_tls.py index cd7616c..f4c233d 100644 --- a/tests/integration/mongodb/test_mongodb_tls.py +++ b/tests/integration/mongodb/test_mongodb_tls.py @@ -10,8 +10,8 @@ class TestMongoDBTLSIntegration: def test_connection_with_tls_disabled(self, mongodb_container): """Test MongoDB connection with TLS disabled.""" - from ddcDatabases import MongoDB - from ddcDatabases.mongodb import MongoDBTLSConfig + from ddcdatabases import MongoDB + from ddcdatabases.mongodb import MongoDBTLSConfig port = mongodb_container.get_exposed_port(27017) host = f"{mongodb_container.get_container_host_ip()}:{port}" @@ -38,8 +38,8 @@ def test_connection_with_tls_disabled(self, mongodb_container): def test_tls_config_is_accessible(self, mongodb_container): """Test that MongoDB TLS config is accessible.""" - from ddcDatabases import MongoDB - from ddcDatabases.mongodb import MongoDBTLSConfig + from ddcdatabases import MongoDB + from ddcdatabases.mongodb import MongoDBTLSConfig port = mongodb_container.get_exposed_port(27017) host = f"{mongodb_container.get_container_host_ip()}:{port}" @@ -62,8 +62,8 @@ def test_tls_config_is_accessible(self, mongodb_container): def test_tls_config_immutable(self, mongodb_container): """Test that MongoDB TLS config is immutable.""" - from ddcDatabases import MongoDB - from ddcDatabases.mongodb import MongoDBTLSConfig + from ddcdatabases import MongoDB + from ddcdatabases.mongodb import MongoDBTLSConfig port = mongodb_container.get_exposed_port(27017) host = f"{mongodb_container.get_container_host_ip()}:{port}" diff --git a/tests/integration/mssql/test_mssql.py b/tests/integration/mssql/test_mssql.py index dfb17a5..65dfbd1 100644 --- a/tests/integration/mssql/test_mssql.py +++ b/tests/integration/mssql/test_mssql.py @@ -10,7 +10,7 @@ class TestMSSQLIntegration: def test_sync_connection(self, mssql_container): """Test synchronous MSSQL connection and CRUD operations.""" - from ddcDatabases import MSSQL, DBUtils + from ddcdatabases import MSSQL, DBUtils port = mssql_container.get_exposed_port(1433) host = mssql_container.get_container_host_ip() @@ -61,7 +61,7 @@ def test_sync_connection(self, mssql_container): @pytest.mark.asyncio async def test_async_connection(self, mssql_container): """Test asynchronous MSSQL connection and CRUD operations.""" - from ddcDatabases import MSSQL, DBUtilsAsync + from ddcdatabases import MSSQL, DBUtilsAsync port = mssql_container.get_exposed_port(1433) host = mssql_container.get_container_host_ip() @@ -104,12 +104,12 @@ async def test_async_connection(self, mssql_container): def test_ssl_encrypt_connection(self, mssql_container): """Test MSSQL with SSL encrypt enabled.""" - from ddcDatabases import MSSQL + from ddcdatabases import MSSQL port = mssql_container.get_exposed_port(1433) host = mssql_container.get_container_host_ip() - from ddcDatabases.mssql import MSSQLSSLConfig + from ddcdatabases.mssql import MSSQLSSLConfig with MSSQL( host=host, diff --git a/tests/integration/mssql/test_mssql_persistent.py b/tests/integration/mssql/test_mssql_persistent.py index 689009a..8adf61d 100644 --- a/tests/integration/mssql/test_mssql_persistent.py +++ b/tests/integration/mssql/test_mssql_persistent.py @@ -4,7 +4,7 @@ import sqlalchemy as sa # noinspection PyProtectedMember -from ddcDatabases.core.persistent import ( +from ddcdatabases.core.persistent import ( MSSQLPersistent, close_all_persistent_connections, ) diff --git a/tests/integration/mssql/test_mssql_ssl.py b/tests/integration/mssql/test_mssql_ssl.py index e9c4a06..1ecb358 100644 --- a/tests/integration/mssql/test_mssql_ssl.py +++ b/tests/integration/mssql/test_mssql_ssl.py @@ -11,8 +11,8 @@ class TestMSSQLSSLIntegration: def test_connection_with_ssl_disabled(self, mssql_container): """Test MSSQL connection with SSL/encryption disabled.""" - from ddcDatabases import MSSQL - from ddcDatabases.mssql import MSSQLSSLConfig + from ddcdatabases import MSSQL + from ddcdatabases.mssql import MSSQLSSLConfig port = mssql_container.get_exposed_port(1433) host = mssql_container.get_container_host_ip() @@ -30,8 +30,8 @@ def test_connection_with_ssl_disabled(self, mssql_container): def test_ssl_config_is_accessible(self, mssql_container): """Test that MSSQL SSL config is accessible.""" - from ddcDatabases import MSSQL - from ddcDatabases.mssql import MSSQLSSLConfig + from ddcdatabases import MSSQL + from ddcdatabases.mssql import MSSQLSSLConfig port = mssql_container.get_exposed_port(1433) host = mssql_container.get_container_host_ip() @@ -51,8 +51,8 @@ def test_ssl_config_is_accessible(self, mssql_container): def test_ssl_config_immutable(self, mssql_container): """Test that MSSQL SSL config is immutable.""" - from ddcDatabases import MSSQL - from ddcDatabases.mssql import MSSQLSSLConfig + from ddcdatabases import MSSQL + from ddcdatabases.mssql import MSSQLSSLConfig port = mssql_container.get_exposed_port(1433) host = mssql_container.get_container_host_ip() @@ -72,8 +72,8 @@ def test_ssl_config_immutable(self, mssql_container): def test_connection_with_trust_certificate(self, mssql_container): """Test MSSQL connection with trust server certificate enabled.""" - from ddcDatabases import MSSQL - from ddcDatabases.mssql import MSSQLSSLConfig + from ddcdatabases import MSSQL + from ddcdatabases.mssql import MSSQLSSLConfig port = mssql_container.get_exposed_port(1433) host = mssql_container.get_container_host_ip() diff --git a/tests/integration/mysql/test_mysql.py b/tests/integration/mysql/test_mysql.py index 80f8955..77126ad 100644 --- a/tests/integration/mysql/test_mysql.py +++ b/tests/integration/mysql/test_mysql.py @@ -10,7 +10,7 @@ class TestMySQLIntegration: def test_sync_connection(self, mysql_container): """Test synchronous MySQL connection and CRUD operations.""" - from ddcDatabases import DBUtils, MySQL + from ddcdatabases import DBUtils, MySQL port = mysql_container.get_exposed_port(3306) host = mysql_container.get_container_host_ip() @@ -61,7 +61,7 @@ def test_sync_connection(self, mysql_container): @pytest.mark.asyncio async def test_async_connection(self, mysql_container): """Test asynchronous MySQL connection and CRUD operations.""" - from ddcDatabases import DBUtilsAsync, MySQL + from ddcdatabases import DBUtilsAsync, MySQL port = mysql_container.get_exposed_port(3306) host = mysql_container.get_container_host_ip() @@ -104,12 +104,12 @@ async def test_async_connection(self, mysql_container): def test_pool_size_configuration(self, mysql_container): """Test MySQL pool size configuration works with real connection.""" - from ddcDatabases import MySQL + from ddcdatabases import MySQL port = mysql_container.get_exposed_port(3306) host = mysql_container.get_container_host_ip() - from ddcDatabases.mysql import MySQLPoolConfig + from ddcdatabases.mysql import MySQLPoolConfig with MySQL( host=host, @@ -124,7 +124,7 @@ def test_pool_size_configuration(self, mysql_container): def test_fetchvalue(self, mysql_container): """Test fetchvalue utility method.""" - from ddcDatabases import DBUtils, MySQL + from ddcdatabases import DBUtils, MySQL port = mysql_container.get_exposed_port(3306) host = mysql_container.get_container_host_ip() diff --git a/tests/integration/mysql/test_mysql_persistent.py b/tests/integration/mysql/test_mysql_persistent.py index a5b45d6..d57082a 100644 --- a/tests/integration/mysql/test_mysql_persistent.py +++ b/tests/integration/mysql/test_mysql_persistent.py @@ -4,7 +4,7 @@ import sqlalchemy as sa # noinspection PyProtectedMember -from ddcDatabases.core.persistent import ( +from ddcdatabases.core.persistent import ( MySQLPersistent, close_all_persistent_connections, ) diff --git a/tests/integration/mysql/test_mysql_ssl.py b/tests/integration/mysql/test_mysql_ssl.py index 9832c06..63b62b3 100644 --- a/tests/integration/mysql/test_mysql_ssl.py +++ b/tests/integration/mysql/test_mysql_ssl.py @@ -11,8 +11,8 @@ class TestMySQLSSLIntegration: def test_connection_with_ssl_disabled(self, mysql_container): """Test MySQL connection with SSL explicitly disabled.""" - from ddcDatabases import MySQL - from ddcDatabases.mysql import MySQLSSLConfig + from ddcdatabases import MySQL + from ddcdatabases.mysql import MySQLSSLConfig port = mysql_container.get_exposed_port(3306) host = mysql_container.get_container_host_ip() @@ -30,8 +30,8 @@ def test_connection_with_ssl_disabled(self, mysql_container): def test_connection_with_ssl_preferred(self, mysql_container): """Test MySQL connection with SSL preferred mode.""" - from ddcDatabases import MySQL - from ddcDatabases.mysql import MySQLSSLConfig + from ddcdatabases import MySQL + from ddcdatabases.mysql import MySQLSSLConfig port = mysql_container.get_exposed_port(3306) host = mysql_container.get_container_host_ip() @@ -49,8 +49,8 @@ def test_connection_with_ssl_preferred(self, mysql_container): def test_ssl_config_is_accessible(self, mysql_container): """Test that MySQL SSL config is accessible.""" - from ddcDatabases import MySQL - from ddcDatabases.mysql import MySQLSSLConfig + from ddcdatabases import MySQL + from ddcdatabases.mysql import MySQLSSLConfig port = mysql_container.get_exposed_port(3306) host = mysql_container.get_container_host_ip() @@ -73,8 +73,8 @@ def test_ssl_config_is_accessible(self, mysql_container): def test_ssl_config_immutable(self, mysql_container): """Test that MySQL SSL config is immutable.""" - from ddcDatabases import MySQL - from ddcDatabases.mysql import MySQLSSLConfig + from ddcdatabases import MySQL + from ddcdatabases.mysql import MySQLSSLConfig port = mysql_container.get_exposed_port(3306) host = mysql_container.get_container_host_ip() @@ -94,14 +94,14 @@ def test_ssl_config_immutable(self, mysql_container): def test_invalid_ssl_mode(self): """Test MySQL rejects invalid SSL mode.""" - from ddcDatabases.mysql import MySQLSSLConfig + from ddcdatabases.mysql import MySQLSSLConfig with pytest.raises(ValueError, match="ssl_mode must be one of"): MySQLSSLConfig(ssl_mode="invalid_mode") def test_valid_ssl_modes(self): """Test MySQL accepts all valid SSL modes.""" - from ddcDatabases.mysql import MySQLSSLConfig + from ddcdatabases.mysql import MySQLSSLConfig valid_modes = ["DISABLED", "PREFERRED", "REQUIRED", "VERIFY_CA", "VERIFY_IDENTITY"] for mode in valid_modes: diff --git a/tests/integration/oracle/test_oracle.py b/tests/integration/oracle/test_oracle.py index 8e43c3f..95a01f8 100644 --- a/tests/integration/oracle/test_oracle.py +++ b/tests/integration/oracle/test_oracle.py @@ -10,7 +10,7 @@ class TestOracleIntegration: def test_sync_connection(self, oracle_container): """Test synchronous Oracle connection and CRUD operations.""" - from ddcDatabases import DBUtils, Oracle + from ddcdatabases import DBUtils, Oracle port = oracle_container.get_exposed_port(1521) host = oracle_container.get_container_host_ip() @@ -60,7 +60,7 @@ def test_sync_connection(self, oracle_container): def test_oracle_dual_query(self, oracle_container): """Test Oracle-specific SELECT FROM dual.""" - from ddcDatabases import Oracle + from ddcdatabases import Oracle port = oracle_container.get_exposed_port(1521) host = oracle_container.get_container_host_ip() @@ -77,12 +77,12 @@ def test_oracle_dual_query(self, oracle_container): def test_pool_configuration(self, oracle_container): """Test Oracle pool configuration works with real connection.""" - from ddcDatabases import Oracle + from ddcdatabases import Oracle port = oracle_container.get_exposed_port(1521) host = oracle_container.get_container_host_ip() - from ddcDatabases.oracle import OraclePoolConfig + from ddcdatabases.oracle import OraclePoolConfig with Oracle( host=host, diff --git a/tests/integration/oracle/test_oracle_persistent.py b/tests/integration/oracle/test_oracle_persistent.py index 26cc7a0..5ab7125 100644 --- a/tests/integration/oracle/test_oracle_persistent.py +++ b/tests/integration/oracle/test_oracle_persistent.py @@ -4,7 +4,7 @@ import sqlalchemy as sa # noinspection PyProtectedMember -from ddcDatabases.core.persistent import ( +from ddcdatabases.core.persistent import ( OraclePersistent, close_all_persistent_connections, ) diff --git a/tests/integration/oracle/test_oracle_ssl.py b/tests/integration/oracle/test_oracle_ssl.py index 84fd2d5..613189f 100644 --- a/tests/integration/oracle/test_oracle_ssl.py +++ b/tests/integration/oracle/test_oracle_ssl.py @@ -11,8 +11,8 @@ class TestOracleSSLIntegration: def test_connection_with_ssl_disabled(self, oracle_container): """Test Oracle connection with SSL disabled.""" - from ddcDatabases import Oracle - from ddcDatabases.oracle import OracleSSLConfig + from ddcdatabases import Oracle + from ddcdatabases.oracle import OracleSSLConfig port = oracle_container.get_exposed_port(1521) host = oracle_container.get_container_host_ip() @@ -30,8 +30,8 @@ def test_connection_with_ssl_disabled(self, oracle_container): def test_ssl_config_is_accessible(self, oracle_container): """Test that Oracle SSL config is accessible.""" - from ddcDatabases import Oracle - from ddcDatabases.oracle import OracleSSLConfig + from ddcdatabases import Oracle + from ddcdatabases.oracle import OracleSSLConfig port = oracle_container.get_exposed_port(1521) host = oracle_container.get_container_host_ip() @@ -51,8 +51,8 @@ def test_ssl_config_is_accessible(self, oracle_container): def test_ssl_config_immutable(self, oracle_container): """Test that Oracle SSL config is immutable.""" - from ddcDatabases import Oracle - from ddcDatabases.oracle import OracleSSLConfig + from ddcdatabases import Oracle + from ddcdatabases.oracle import OracleSSLConfig port = oracle_container.get_exposed_port(1521) host = oracle_container.get_container_host_ip() diff --git a/tests/integration/postgresql/test_postgresql.py b/tests/integration/postgresql/test_postgresql.py index 9c30278..cc0adda 100644 --- a/tests/integration/postgresql/test_postgresql.py +++ b/tests/integration/postgresql/test_postgresql.py @@ -10,7 +10,7 @@ class TestPostgreSQLIntegration: def test_sync_connection(self, postgres_container): """Test synchronous PostgreSQL connection and CRUD operations.""" - from ddcDatabases import DBUtils, PostgreSQL + from ddcdatabases import DBUtils, PostgreSQL port = postgres_container.get_exposed_port(5432) host = postgres_container.get_container_host_ip() @@ -61,7 +61,7 @@ def test_sync_connection(self, postgres_container): @pytest.mark.asyncio async def test_async_connection(self, postgres_container): """Test asynchronous PostgreSQL connection and CRUD operations.""" - from ddcDatabases import DBUtilsAsync, PostgreSQL + from ddcdatabases import DBUtilsAsync, PostgreSQL port = postgres_container.get_exposed_port(5432) host = postgres_container.get_container_host_ip() @@ -104,7 +104,7 @@ async def test_async_connection(self, postgres_container): def test_schema_support(self, postgres_container): """Test PostgreSQL schema parameter.""" - from ddcDatabases import PostgreSQL + from ddcdatabases import PostgreSQL port = postgres_container.get_exposed_port(5432) host = postgres_container.get_container_host_ip() @@ -123,7 +123,7 @@ def test_schema_support(self, postgres_container): def test_fetchvalue(self, postgres_container): """Test fetchvalue utility method.""" - from ddcDatabases import DBUtils, PostgreSQL + from ddcdatabases import DBUtils, PostgreSQL port = postgres_container.get_exposed_port(5432) host = postgres_container.get_container_host_ip() @@ -153,7 +153,7 @@ def test_fetchvalue(self, postgres_container): def test_multi_schema_search_path(self, postgres_container): """Test PostgreSQL with comma-separated schemas in search_path.""" - from ddcDatabases import PostgreSQL + from ddcdatabases import PostgreSQL port = postgres_container.get_exposed_port(5432) host = postgres_container.get_container_host_ip() @@ -198,7 +198,7 @@ def test_multi_schema_search_path(self, postgres_container): @pytest.mark.asyncio async def test_multi_schema_search_path_async(self, postgres_container): """Test async PostgreSQL with comma-separated schemas in search_path.""" - from ddcDatabases import PostgreSQL + from ddcdatabases import PostgreSQL port = postgres_container.get_exposed_port(5432) host = postgres_container.get_container_host_ip() diff --git a/tests/integration/postgresql/test_postgresql_persistent.py b/tests/integration/postgresql/test_postgresql_persistent.py index 88ae16c..a2fe446 100644 --- a/tests/integration/postgresql/test_postgresql_persistent.py +++ b/tests/integration/postgresql/test_postgresql_persistent.py @@ -4,7 +4,7 @@ import sqlalchemy as sa # noinspection PyProtectedMember -from ddcDatabases.core.persistent import ( +from ddcdatabases.core.persistent import ( PostgreSQLPersistent, close_all_persistent_connections, ) diff --git a/tests/integration/postgresql/test_postgresql_ssl.py b/tests/integration/postgresql/test_postgresql_ssl.py index f7337d8..b24ac10 100644 --- a/tests/integration/postgresql/test_postgresql_ssl.py +++ b/tests/integration/postgresql/test_postgresql_ssl.py @@ -11,8 +11,8 @@ class TestPostgreSQLSSLIntegration: def test_connection_with_ssl_disabled(self, postgres_container): """Test PostgreSQL connection with SSL explicitly disabled.""" - from ddcDatabases import PostgreSQL - from ddcDatabases.postgresql import PostgreSQLSSLConfig + from ddcdatabases import PostgreSQL + from ddcdatabases.postgresql import PostgreSQLSSLConfig port = postgres_container.get_exposed_port(5432) host = postgres_container.get_container_host_ip() @@ -30,8 +30,8 @@ def test_connection_with_ssl_disabled(self, postgres_container): def test_connection_with_ssl_prefer(self, postgres_container): """Test PostgreSQL connection with SSL prefer mode (falls back to no SSL).""" - from ddcDatabases import PostgreSQL - from ddcDatabases.postgresql import PostgreSQLSSLConfig + from ddcdatabases import PostgreSQL + from ddcdatabases.postgresql import PostgreSQLSSLConfig port = postgres_container.get_exposed_port(5432) host = postgres_container.get_container_host_ip() @@ -49,8 +49,8 @@ def test_connection_with_ssl_prefer(self, postgres_container): def test_connection_with_ssl_allow(self, postgres_container): """Test PostgreSQL connection with SSL allow mode.""" - from ddcDatabases import PostgreSQL - from ddcDatabases.postgresql import PostgreSQLSSLConfig + from ddcdatabases import PostgreSQL + from ddcdatabases.postgresql import PostgreSQLSSLConfig port = postgres_container.get_exposed_port(5432) host = postgres_container.get_container_host_ip() @@ -68,8 +68,8 @@ def test_connection_with_ssl_allow(self, postgres_container): def test_ssl_config_is_accessible(self, postgres_container): """Test that SSL config is accessible through getter method.""" - from ddcDatabases import PostgreSQL - from ddcDatabases.postgresql import PostgreSQLSSLConfig + from ddcdatabases import PostgreSQL + from ddcdatabases.postgresql import PostgreSQLSSLConfig port = postgres_container.get_exposed_port(5432) host = postgres_container.get_container_host_ip() @@ -92,8 +92,8 @@ def test_ssl_config_is_accessible(self, postgres_container): def test_ssl_config_immutable(self, postgres_container): """Test that SSL config is immutable.""" - from ddcDatabases import PostgreSQL - from ddcDatabases.postgresql import PostgreSQLSSLConfig + from ddcdatabases import PostgreSQL + from ddcdatabases.postgresql import PostgreSQLSSLConfig port = postgres_container.get_exposed_port(5432) host = postgres_container.get_container_host_ip() @@ -113,14 +113,14 @@ def test_ssl_config_immutable(self, postgres_container): def test_invalid_ssl_mode(self): """Test PostgreSQL rejects invalid SSL mode.""" - from ddcDatabases.postgresql import PostgreSQLSSLConfig + from ddcdatabases.postgresql import PostgreSQLSSLConfig with pytest.raises(ValueError, match="ssl_mode must be one of"): PostgreSQLSSLConfig(ssl_mode="invalid_mode") def test_valid_ssl_modes(self): """Test PostgreSQL accepts all valid SSL modes.""" - from ddcDatabases.postgresql import PostgreSQLSSLConfig + from ddcdatabases.postgresql import PostgreSQLSSLConfig valid_modes = ["disable", "allow", "prefer", "require", "verify-ca", "verify-full"] for mode in valid_modes: diff --git a/tests/integration/sqlite/test_sqlite.py b/tests/integration/sqlite/test_sqlite.py index 6d3bf01..881d18f 100644 --- a/tests/integration/sqlite/test_sqlite.py +++ b/tests/integration/sqlite/test_sqlite.py @@ -12,7 +12,7 @@ class TestSqliteIntegration: def test_sync_memory_connection(self): """Test synchronous SQLite in-memory connection and CRUD operations.""" - from ddcDatabases import DBUtils, Sqlite + from ddcdatabases import DBUtils, Sqlite from sqlalchemy.pool import StaticPool with Sqlite(filepath=":memory:", extra_engine_args={"poolclass": StaticPool}) as session: @@ -54,7 +54,7 @@ def test_sync_memory_connection(self): def test_sync_file_connection(self): """Test synchronous SQLite file-based connection.""" - from ddcDatabases import DBUtils, Sqlite + from ddcdatabases import DBUtils, Sqlite with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f: db_path = f.name @@ -80,7 +80,7 @@ def test_sync_file_connection(self): def test_fetchvalue(self): """Test fetchvalue utility method with SQLite.""" - from ddcDatabases import DBUtils, Sqlite + from ddcdatabases import DBUtils, Sqlite from sqlalchemy.pool import StaticPool with Sqlite(filepath=":memory:", extra_engine_args={"poolclass": StaticPool}) as session: @@ -98,7 +98,7 @@ def test_fetchvalue(self): def test_multiple_inserts(self): """Test multiple inserts and fetchall with SQLite.""" - from ddcDatabases import DBUtils, Sqlite + from ddcdatabases import DBUtils, Sqlite from sqlalchemy.pool import StaticPool with Sqlite(filepath=":memory:", extra_engine_args={"poolclass": StaticPool}) as session: diff --git a/tests/smoke_test.py b/tests/smoke_test.py index 25b18d1..5084c41 100644 --- a/tests/smoke_test.py +++ b/tests/smoke_test.py @@ -1,9 +1,9 @@ """Smoke test to verify the built package works correctly.""" -from ddcDatabases import DBUtils, DBUtilsAsync, __version__ +from ddcdatabases import DBUtils, DBUtilsAsync, __version__ assert __version__, "Version should not be empty" assert DBUtils, "DBUtils should be importable" assert DBUtilsAsync, "DBUtilsAsync should be importable" -print(f"ddcDatabases {__version__} OK") +print(f"ddcdatabases {__version__} OK") diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 9eceaaf..bb5083b 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -5,8 +5,8 @@ def clear_settings_cache(): """Clear all settings caches before and after each test to ensure isolation.""" try: - import ddcDatabases.core.settings - from ddcDatabases.core.settings import ( + import ddcdatabases.core.settings + from ddcdatabases.core.settings import ( get_mongodb_settings, get_mssql_settings, get_mysql_settings, @@ -21,7 +21,7 @@ def clear_settings_cache(): get_mysql_settings.cache_clear() get_mongodb_settings.cache_clear() get_oracle_settings.cache_clear() - ddcDatabases.core.settings._dotenv_loaded = False + ddcdatabases.core.settings._dotenv_loaded = False except ImportError: pass @@ -34,6 +34,6 @@ def clear_settings_cache(): get_mysql_settings.cache_clear() get_mongodb_settings.cache_clear() get_oracle_settings.cache_clear() - ddcDatabases.core.settings._dotenv_loaded = False + ddcdatabases.core.settings._dotenv_loaded = False except (NameError, ImportError): pass diff --git a/tests/unit/core/test_async_functionality.py b/tests/unit/core/test_async_functionality.py index 0870367..006b59e 100644 --- a/tests/unit/core/test_async_functionality.py +++ b/tests/unit/core/test_async_functionality.py @@ -30,7 +30,7 @@ class ConcreteAsyncTestConnection: @staticmethod def create_test_connection(connection_url, engine_args, autoflush, expire_on_commit, sync_driver, async_driver): """Create a concrete test implementation of BaseConnection""" - from ddcDatabases.core.base import BaseConnection + from ddcdatabases.core.base import BaseConnection class TestableAsyncBaseConnection(BaseConnection): @contextmanager @@ -82,7 +82,7 @@ class TestAsyncBaseConnection: def setup_method(self): """Import dependencies when needed""" - from ddcDatabases.core.base import BaseConnection + from ddcdatabases.core.base import BaseConnection self.BaseConnection = BaseConnection @@ -102,7 +102,7 @@ async def test_async_context_manager_entry(self): with ( patch.object(conn, "_get_async_engine") as mock_get_engine, - patch("ddcDatabases.core.base.async_sessionmaker") as mock_sessionmaker, + patch("ddcdatabases.core.base.async_sessionmaker") as mock_sessionmaker, patch.object(conn, "_test_connection_async") as mock_test_conn, ): mock_engine = AsyncMock() @@ -182,7 +182,7 @@ class TestDBUtilsAsync: def setup_method(self): """Import dependencies when needed""" - from ddcDatabases import DBUtilsAsync + from ddcdatabases import DBUtilsAsync self.DBUtilsAsync = DBUtilsAsync @@ -386,7 +386,7 @@ class TestAsyncIntegration: def setup_method(self): """Import dependencies when needed""" - from ddcDatabases import DBUtilsAsync + from ddcdatabases import DBUtilsAsync self.DBUtilsAsync = DBUtilsAsync @@ -488,8 +488,8 @@ class TestAsyncCompatibility: def setup_method(self): """Import dependencies when needed""" - from ddcDatabases import DBUtilsAsync - from ddcDatabases.core.base import BaseConnection + from ddcdatabases import DBUtilsAsync + from ddcdatabases.core.base import BaseConnection self.DBUtilsAsync = DBUtilsAsync self.BaseConnection = BaseConnection diff --git a/tests/unit/core/test_db_utils.py b/tests/unit/core/test_db_utils.py index c6e192d..daffd16 100644 --- a/tests/unit/core/test_db_utils.py +++ b/tests/unit/core/test_db_utils.py @@ -39,7 +39,7 @@ def create_test_connection( operation_retry_config=None, ): """Create a concrete test implementation of BaseConnection""" - from ddcDatabases.core.base import BaseConnection + from ddcdatabases.core.base import BaseConnection class TestableBaseConnection(BaseConnection): @contextmanager @@ -92,7 +92,7 @@ class TestBaseConnection: def setup_method(self): """Import dependencies when needed""" - from ddcDatabases.core.base import BaseConnection, ConnectionTester + from ddcdatabases.core.base import BaseConnection, ConnectionTester self.BaseConnection = BaseConnection self.ConnectionTester = ConnectionTester @@ -198,7 +198,7 @@ class TestDBUtils: def setup_method(self): """Import dependencies when needed""" - from ddcDatabases import DBUtils + from ddcdatabases import DBUtils self.DBUtils = DBUtils @@ -442,7 +442,7 @@ class TestDBUtilsAsync: def setup_method(self): """Import dependencies when needed""" - from ddcDatabases import DBUtilsAsync + from ddcdatabases import DBUtilsAsync self.DBUtilsAsync = DBUtilsAsync @@ -637,14 +637,14 @@ class TestBaseConnectionContextManagers: def setup_method(self): """Import dependencies when needed""" - from ddcDatabases.core.base import BaseConnection, ConnectionTester + from ddcdatabases.core.base import BaseConnection, ConnectionTester self.BaseConnection = BaseConnection self.ConnectionTester = ConnectionTester def test_sync_context_manager(self): """Test sync context manager __enter__ and __exit__ methods""" - from ddcDatabases.core.configs import BaseRetryConfig + from ddcdatabases.core.configs import BaseRetryConfig connection_url = {"host": "localhost", "database": "test"} engine_args = {"echo": False} @@ -664,7 +664,7 @@ def test_sync_context_manager(self): with ( patch.object(conn, "_get_engine") as mock_get_engine, - patch("ddcDatabases.core.base.sessionmaker") as mock_sessionmaker, + patch("ddcdatabases.core.base.sessionmaker") as mock_sessionmaker, patch.object(conn, "_test_connection_sync") as mock_test_conn, ): mock_get_engine.return_value.__enter__.return_value = mock_engine @@ -705,7 +705,7 @@ async def test_async_context_manager(self): with ( patch.object(conn, "_get_async_engine") as mock_get_engine, - patch("ddcDatabases.core.base.async_sessionmaker") as mock_sessionmaker, + patch("ddcdatabases.core.base.async_sessionmaker") as mock_sessionmaker, patch.object(conn, "_test_connection_async") as mock_test_conn, ): mock_get_engine.return_value.__aenter__.return_value = mock_engine @@ -791,7 +791,7 @@ def test_test_connection_sync_method(self): mock_session = MagicMock() - with patch("ddcDatabases.core.base.ConnectionTester") as mock_tester_class: + with patch("ddcdatabases.core.base.ConnectionTester") as mock_tester_class: mock_tester = MagicMock() mock_tester_class.return_value = mock_tester @@ -824,7 +824,7 @@ async def test_test_connection_async_method(self): mock_session = AsyncMock() - with patch("ddcDatabases.core.base.ConnectionTester") as mock_tester_class: + with patch("ddcdatabases.core.base.ConnectionTester") as mock_tester_class: mock_tester = MagicMock() mock_tester.test_connection_async = AsyncMock() mock_tester_class.return_value = mock_tester @@ -848,7 +848,7 @@ class TestConnectionTesterCoverage: def setup_method(self): """Import dependencies when needed""" - from ddcDatabases.core.base import ConnectionTester + from ddcdatabases.core.base import ConnectionTester self.ConnectionTester = ConnectionTester @@ -887,7 +887,7 @@ class TestDBUtilsAsyncInsertBulk: def setup_method(self): """Import dependencies when needed""" - from ddcDatabases import DBUtilsAsync + from ddcdatabases import DBUtilsAsync self.DBUtilsAsync = DBUtilsAsync diff --git a/tests/unit/core/test_exceptions.py b/tests/unit/core/test_exceptions.py index cebcea2..b015e63 100644 --- a/tests/unit/core/test_exceptions.py +++ b/tests/unit/core/test_exceptions.py @@ -1,5 +1,5 @@ import pytest -from ddcDatabases.core.exceptions import ( +from ddcdatabases.core.exceptions import ( CustomBaseException, DBDeleteAllDataException, DBExecuteException, diff --git a/tests/unit/core/test_init_module.py b/tests/unit/core/test_init_module.py index 98c5393..6e1e606 100644 --- a/tests/unit/core/test_init_module.py +++ b/tests/unit/core/test_init_module.py @@ -6,7 +6,7 @@ def test_logging_configuration(self): """Test that logging is properly configured with NullHandler""" import logging - logger = logging.getLogger("ddcDatabases") + logger = logging.getLogger("ddcdatabases") handlers = logger.handlers # Should have at least one NullHandler @@ -23,18 +23,18 @@ def test_version_parsing_module_not_found(self): try: # Create a test scenario that will trigger ModuleNotFoundError - if "ddcDatabases" in sys.modules: - del sys.modules["ddcDatabases"] + if "ddcdatabases" in sys.modules: + del sys.modules["ddcdatabases"] # Mock the version function to raise ModuleNotFoundError with patch("importlib.metadata.version") as mock_version: - mock_version.side_effect = ModuleNotFoundError("No module named 'ddcDatabases'") + mock_version.side_effect = ModuleNotFoundError("No module named 'ddcdatabases'") # This should trigger the exception handling code try: from importlib.metadata import version - _version = tuple(int(x) for x in version("ddcDatabases").split(".")) + _version = tuple(int(x) for x in version("ddcdatabases").split(".")) except ModuleNotFoundError: _version = (0, 0, 0) @@ -55,10 +55,10 @@ def test_version_parsing_exception_path_direct(self): try: # Force a re-import with mocked version to test the exception path with patch("importlib.metadata.version") as mock_version: - mock_version.side_effect = ModuleNotFoundError("No module named 'ddcDatabases'") + mock_version.side_effect = ModuleNotFoundError("No module named 'ddcdatabases'") # Remove the module from cache if it exists - modules_to_remove = [name for name in sys.modules.keys() if name.startswith("ddcDatabases")] + modules_to_remove = [name for name in sys.modules.keys() if name.startswith("ddcdatabases")] for module_name in modules_to_remove: del sys.modules[module_name] @@ -74,7 +74,7 @@ def test_version_parsing_exception_path_direct(self): def test_constants_accessibility(self): """Test that all module constants are accessible""" - import ddcDatabases + import ddcdatabases constants = [ "__title__", @@ -86,11 +86,11 @@ def test_constants_accessibility(self): ] for const in constants: - assert hasattr(ddcDatabases, const), f"Constant {const} not accessible" + assert hasattr(ddcdatabases, const), f"Constant {const} not accessible" def test_all_exports_defined(self): """Test that __all__ contains valid exports and core classes are always present""" - import ddcDatabases + import ddcdatabases # Core exports that should always be present (no optional dependencies) core_exports = { @@ -101,14 +101,14 @@ def test_all_exports_defined(self): } # Verify core exports are always present - assert core_exports.issubset(set(ddcDatabases.__all__)), "Core exports missing from __all__" + assert core_exports.issubset(set(ddcdatabases.__all__)), "Core exports missing from __all__" # Verify all items in __all__ are actually accessible - for name in ddcDatabases.__all__: - assert hasattr(ddcDatabases, name), f"{name} in __all__ but not accessible" + for name in ddcdatabases.__all__: + assert hasattr(ddcdatabases, name), f"{name} in __all__ but not accessible" # Verify __all__ is a tuple (converted at end of __init__.py) - assert isinstance(ddcDatabases.__all__, tuple), "__all__ should be a tuple" + assert isinstance(ddcdatabases.__all__, tuple), "__all__ should be a tuple" def test_version_string_parsing_edge_cases(self): """Test version string parsing with various formats""" @@ -124,21 +124,21 @@ def test_version_string_parsing_edge_cases(self): def test_metadata_string_values(self): """Test metadata string values are correct""" - import ddcDatabases + import ddcdatabases # Test specific metadata values - assert isinstance(ddcDatabases.__title__, str) - assert isinstance(ddcDatabases.__author__, str) - assert isinstance(ddcDatabases.__email__, str) - assert isinstance(ddcDatabases.__license__, str) - assert isinstance(ddcDatabases.__copyright__, str) + assert isinstance(ddcdatabases.__title__, str) + assert isinstance(ddcdatabases.__author__, str) + assert isinstance(ddcdatabases.__email__, str) + assert isinstance(ddcdatabases.__license__, str) + assert isinstance(ddcdatabases.__copyright__, str) # Test they're not empty - assert len(ddcDatabases.__title__) > 0 - assert len(ddcDatabases.__author__) > 0 - assert len(ddcDatabases.__email__) > 0 - assert len(ddcDatabases.__license__) > 0 - assert len(ddcDatabases.__copyright__) > 0 + assert len(ddcdatabases.__title__) > 0 + assert len(ddcdatabases.__author__) > 0 + assert len(ddcdatabases.__email__) > 0 + assert len(ddcdatabases.__license__) > 0 + assert len(ddcdatabases.__copyright__) > 0 def test_force_module_not_found_error_direct(self): """Force the ModuleNotFoundError by testing the logic directly""" @@ -157,7 +157,7 @@ def test_force_module_not_found_error_direct(self): def test_main_imports(self): """Test main module imports work correctly""" - from ddcDatabases import ( + from ddcdatabases import ( MSSQL, DBUtils, DBUtilsAsync, @@ -178,13 +178,13 @@ def test_main_imports(self): def test_mongodb_import_accessibility(self): """Test MongoDB import specifically""" - from ddcDatabases import MongoDB + from ddcdatabases import MongoDB assert MongoDB is not None def test_mariadb_aliases(self): """Test MariaDB aliases point to MySQL classes""" - from ddcDatabases import ( + from ddcdatabases import ( MariaDB, MariaDBConnectionConfig, MariaDBConnectionRetryConfig, diff --git a/tests/unit/core/test_persistent.py b/tests/unit/core/test_persistent.py index 3da5a8d..04c7c2d 100644 --- a/tests/unit/core/test_persistent.py +++ b/tests/unit/core/test_persistent.py @@ -3,11 +3,11 @@ import pytest import ssl as _ssl_module import time -from ddcDatabases.core.configs import BaseOperationRetryConfig as OperationRetryConfig -from ddcDatabases.core.configs import BaseRetryConfig as RetryConfig +from ddcdatabases.core.configs import BaseOperationRetryConfig as OperationRetryConfig +from ddcdatabases.core.configs import BaseRetryConfig as RetryConfig # noinspection PyProtectedMember -from ddcDatabases.core.persistent import ( +from ddcdatabases.core.persistent import ( MongoDBPersistent, MSSQLPersistent, MySQLPersistent, @@ -1148,7 +1148,7 @@ class TestRetrySettingsIntegration: def test_postgresql_retry_settings(self): """Test PostgreSQL settings include retry fields.""" - from ddcDatabases.core.settings import PostgreSQLSettings + from ddcdatabases.core.settings import PostgreSQLSettings settings = PostgreSQLSettings() assert hasattr(settings, "connection_enable_retry") @@ -1175,7 +1175,7 @@ def test_postgresql_retry_settings(self): def test_mysql_retry_settings(self): """Test MySQL settings include retry fields.""" - from ddcDatabases.core.settings import MySQLSettings + from ddcdatabases.core.settings import MySQLSettings settings = MySQLSettings() assert settings.connection_enable_retry is True @@ -1186,7 +1186,7 @@ def test_mysql_retry_settings(self): def test_mssql_retry_settings(self): """Test MSSQL settings include retry fields.""" - from ddcDatabases.core.settings import MSSQLSettings + from ddcdatabases.core.settings import MSSQLSettings settings = MSSQLSettings() assert settings.connection_enable_retry is True @@ -1197,7 +1197,7 @@ def test_mssql_retry_settings(self): def test_oracle_retry_settings(self): """Test Oracle settings include retry fields.""" - from ddcDatabases.core.settings import OracleSettings + from ddcdatabases.core.settings import OracleSettings settings = OracleSettings() assert settings.connection_enable_retry is True @@ -1208,7 +1208,7 @@ def test_oracle_retry_settings(self): def test_mongodb_retry_settings(self): """Test MongoDB settings include retry fields.""" - from ddcDatabases.core.settings import MongoDBSettings + from ddcdatabases.core.settings import MongoDBSettings settings = MongoDBSettings() assert settings.connection_enable_retry is True @@ -1219,7 +1219,7 @@ def test_mongodb_retry_settings(self): def test_sqlite_retry_settings(self): """Test SQLite settings include retry fields (minimal).""" - from ddcDatabases.core.settings import SQLiteSettings + from ddcdatabases.core.settings import SQLiteSettings settings = SQLiteSettings() assert settings.connection_enable_retry is False # SQLite disabled by default @@ -1284,7 +1284,7 @@ def _mock_mssql_settings(**overrides): class TestAsyncCreateEngine: """Test PersistentSQLAlchemyAsyncConnection._create_engine.""" - @patch("ddcDatabases.core.persistent.create_async_engine") + @patch("ddcdatabases.core.persistent.create_async_engine") def test_create_async_engine_called(self, mock_create_async_engine): """Test that _create_engine calls create_async_engine with correct args.""" mock_engine = MagicMock() @@ -1314,7 +1314,7 @@ def setup_method(self): def teardown_method(self): close_all_persistent_connections() - @patch("ddcDatabases.core.persistent.get_postgresql_settings") + @patch("ddcdatabases.core.persistent.get_postgresql_settings") def test_ssl_verify_full_with_ca_cert_creates_ssl_context(self, mock_get_settings): """Test async ssl_mode=verify-full with CA cert creates SSLContext.""" mock_get_settings.return_value = _mock_pg_settings( @@ -1336,7 +1336,7 @@ def test_ssl_verify_full_with_ca_cert_creates_ssl_context(self, mock_get_setting assert "ssl" in connect_args assert isinstance(connect_args["ssl"], _ssl_module.SSLContext) - @patch("ddcDatabases.core.persistent.get_postgresql_settings") + @patch("ddcdatabases.core.persistent.get_postgresql_settings") def test_ssl_verify_full_with_client_certs_loads_cert_chain(self, mock_get_settings): """Test async ssl_mode=verify-full with client certs calls load_cert_chain.""" mock_get_settings.return_value = _mock_pg_settings( @@ -1364,7 +1364,7 @@ def test_ssl_verify_full_with_client_certs_loads_cert_chain(self, mock_get_setti keyfile="/path/to/client.key", ) - @patch("ddcDatabases.core.persistent.get_postgresql_settings") + @patch("ddcdatabases.core.persistent.get_postgresql_settings") def test_ssl_verify_full_no_ca_cert_passes_ssl_mode_directly(self, mock_get_settings): """Test async ssl_mode=verify-full with no CA cert passes ssl_mode string.""" mock_get_settings.return_value = _mock_pg_settings( @@ -1393,7 +1393,7 @@ def setup_method(self): def teardown_method(self): close_all_persistent_connections() - @patch("ddcDatabases.core.persistent.get_postgresql_settings") + @patch("ddcdatabases.core.persistent.get_postgresql_settings") def test_ssl_verify_full_with_all_certs_populates_connect_args(self, mock_get_settings): """Test sync ssl_mode=verify-full with CA/client certs populates connect_args.""" mock_get_settings.return_value = _mock_pg_settings( @@ -1418,7 +1418,7 @@ def test_ssl_verify_full_with_all_certs_populates_connect_args(self, mock_get_se assert connect_args["sslcert"] == "/path/to/client.crt" assert connect_args["sslkey"] == "/path/to/client.key" - @patch("ddcDatabases.core.persistent.get_postgresql_settings") + @patch("ddcdatabases.core.persistent.get_postgresql_settings") def test_ssl_verify_full_no_certs_only_sslmode(self, mock_get_settings): """Test sync ssl_mode=verify-full with no certs only sets sslmode.""" mock_get_settings.return_value = _mock_pg_settings( @@ -1450,7 +1450,7 @@ def setup_method(self): def teardown_method(self): close_all_persistent_connections() - @patch("ddcDatabases.core.persistent.get_mysql_settings") + @patch("ddcdatabases.core.persistent.get_mysql_settings") def test_ssl_with_all_certs_populates_connect_args(self, mock_get_settings): """Test MySQL SSL with CA/client certs populates connect_args.""" mock_get_settings.return_value = _mock_mysql_settings( @@ -1475,7 +1475,7 @@ def test_ssl_with_all_certs_populates_connect_args(self, mock_get_settings): assert ssl_dict["cert"] == "/path/to/client.crt" assert ssl_dict["key"] == "/path/to/client.key" - @patch("ddcDatabases.core.persistent.get_mysql_settings") + @patch("ddcdatabases.core.persistent.get_mysql_settings") def test_ssl_with_ca_only(self, mock_get_settings): """Test MySQL SSL with only CA cert.""" mock_get_settings.return_value = _mock_mysql_settings( @@ -1508,7 +1508,7 @@ def setup_method(self): def teardown_method(self): close_all_persistent_connections() - @patch("ddcDatabases.core.persistent.get_mssql_settings") + @patch("ddcdatabases.core.persistent.get_mssql_settings") def test_ssl_ca_cert_path_sets_server_certificate(self, mock_get_settings): """Test with ssl_ca_cert_path set puts ServerCertificate in connection URL query.""" mock_get_settings.return_value = _mock_mssql_settings( diff --git a/tests/unit/core/test_retry_logic.py b/tests/unit/core/test_retry_logic.py index aa60067..37930f5 100644 --- a/tests/unit/core/test_retry_logic.py +++ b/tests/unit/core/test_retry_logic.py @@ -1,11 +1,11 @@ """Tests for retry logic functionality.""" import pytest -from ddcDatabases.core.configs import BaseOperationRetryConfig, BaseRetryConfig -from ddcDatabases.core.constants import CONNECTION_ERROR_KEYWORDS +from ddcdatabases.core.configs import BaseOperationRetryConfig, BaseRetryConfig +from ddcdatabases.core.constants import CONNECTION_ERROR_KEYWORDS # noinspection PyProtectedMember -from ddcDatabases.core.retry import ( +from ddcdatabases.core.retry import ( _calculate_retry_delay, _handle_retry_exception, _is_connection_error, @@ -310,7 +310,7 @@ def test_retry_disabled(self): operation.assert_called_once() - @patch("ddcDatabases.core.retry.time.sleep") + @patch("ddcdatabases.core.retry.time.sleep") def test_delay_between_retries(self, mock_sleep): """Test that delays occur between retries.""" config = BaseOperationRetryConfig( @@ -462,7 +462,7 @@ class TestRetryPolicyInDatabaseClasses: def test_postgresql_retry_config(self): """Test PostgreSQL includes retry config.""" - from ddcDatabases.postgresql import PostgreSQL + from ddcdatabases.postgresql import PostgreSQL pg = PostgreSQL() retry_info = pg.get_connection_retry_info() @@ -474,7 +474,7 @@ def test_postgresql_retry_config(self): def test_mysql_retry_config(self): """Test MySQL includes retry config.""" - from ddcDatabases.mysql import MySQL + from ddcdatabases.mysql import MySQL mysql = MySQL() retry_info = mysql.get_connection_retry_info() @@ -484,7 +484,7 @@ def test_mysql_retry_config(self): def test_mssql_retry_config(self): """Test MSSQL includes retry config.""" - from ddcDatabases.mssql import MSSQL + from ddcdatabases.mssql import MSSQL mssql = MSSQL() retry_info = mssql.get_connection_retry_info() @@ -494,7 +494,7 @@ def test_mssql_retry_config(self): def test_oracle_retry_config(self): """Test Oracle includes retry config.""" - from ddcDatabases.oracle import Oracle + from ddcdatabases.oracle import Oracle oracle = Oracle() retry_info = oracle.get_connection_retry_info() @@ -504,7 +504,7 @@ def test_oracle_retry_config(self): def test_sqlite_retry_config(self): """Test SQLite includes retry config.""" - from ddcDatabases.sqlite import Sqlite + from ddcdatabases.sqlite import Sqlite sqlite = Sqlite() retry_info = sqlite.get_connection_retry_info() @@ -515,7 +515,7 @@ def test_sqlite_retry_config(self): def test_mongodb_retry_config(self): """Test MongoDB includes retry config.""" - from ddcDatabases.mongodb import MongoDB + from ddcdatabases.mongodb import MongoDB mongodb = MongoDB(collection="test") retry_info = mongodb.get_connection_retry_info() @@ -525,7 +525,7 @@ def test_mongodb_retry_config(self): def test_custom_retry_settings(self): """Test passing custom retry settings to database class.""" - from ddcDatabases.postgresql import PostgreSQL, PostgreSQLConnectionRetryConfig + from ddcdatabases.postgresql import PostgreSQL, PostgreSQLConnectionRetryConfig pg = PostgreSQL( connection_retry_config=PostgreSQLConnectionRetryConfig( diff --git a/tests/unit/core/test_settings.py b/tests/unit/core/test_settings.py index 6a9fd3e..d991e50 100644 --- a/tests/unit/core/test_settings.py +++ b/tests/unit/core/test_settings.py @@ -1,6 +1,6 @@ import os import pytest -from ddcDatabases.core.settings import ( +from ddcdatabases.core.settings import ( MongoDBSettings, MSSQLSettings, MySQLSettings, @@ -360,7 +360,7 @@ class TestDotenvLoading: # noinspection PyMethodMayBeStatic def setup_method(self): """Clear all settings caches before each test to ensure isolation""" - from ddcDatabases.core.settings import ( + from ddcdatabases.core.settings import ( get_mongodb_settings, get_mssql_settings, get_mysql_settings, @@ -376,12 +376,12 @@ def setup_method(self): get_mongodb_settings.cache_clear() get_oracle_settings.cache_clear() # Reset dotenv flag to ensure clean state - import ddcDatabases.core.settings + import ddcdatabases.core.settings - ddcDatabases.core.settings._dotenv_loaded = False + ddcdatabases.core.settings._dotenv_loaded = False - @patch("ddcDatabases.core.settings._dotenv_loaded", True) - @patch("ddcDatabases.core.settings.load_dotenv") + @patch("ddcdatabases.core.settings._dotenv_loaded", True) + @patch("ddcdatabases.core.settings.load_dotenv") def test_dotenv_not_loaded_if_already_loaded(self, mock_load_dotenv): """Test that dotenv is not loaded if already loaded""" get_postgresql_settings.cache_clear() @@ -398,7 +398,7 @@ def test_dotenv_loading_flag(self): Note: This test is skipped due to test isolation issues. The dotenv loading behavior is indirectly verified by other tests that successfully use settings. """ - import ddcDatabases.core.settings as settings_module + import ddcdatabases.core.settings as settings_module # Patch load_dotenv before reloading to ensure it's mocked with patch.object(settings_module, "load_dotenv") as mock_load_dotenv: diff --git a/tests/unit/core/test_ssl_configs.py b/tests/unit/core/test_ssl_configs.py index 4f44ffe..e60416e 100644 --- a/tests/unit/core/test_ssl_configs.py +++ b/tests/unit/core/test_ssl_configs.py @@ -6,28 +6,28 @@ class TestSSLConstantsIntegrity: def test_postgresql_ssl_modes_frozenset(self): """Test that PostgreSQL SSL modes is a frozenset.""" - from ddcDatabases.core.constants import POSTGRESQL_SSL_MODES + from ddcdatabases.core.constants import POSTGRESQL_SSL_MODES assert isinstance(POSTGRESQL_SSL_MODES, frozenset) assert len(POSTGRESQL_SSL_MODES) == 6 def test_mysql_ssl_modes_frozenset(self): """Test that MySQL SSL modes is a frozenset.""" - from ddcDatabases.core.constants import MYSQL_SSL_MODES + from ddcdatabases.core.constants import MYSQL_SSL_MODES assert isinstance(MYSQL_SSL_MODES, frozenset) assert len(MYSQL_SSL_MODES) == 5 def test_postgresql_modes_content(self): """Test PostgreSQL SSL modes contain expected values.""" - from ddcDatabases.core.constants import POSTGRESQL_SSL_MODES + from ddcdatabases.core.constants import POSTGRESQL_SSL_MODES expected_modes = {"disable", "allow", "prefer", "require", "verify-ca", "verify-full"} assert POSTGRESQL_SSL_MODES == expected_modes def test_mysql_modes_content(self): """Test MySQL SSL modes contain expected values.""" - from ddcDatabases.core.constants import MYSQL_SSL_MODES + from ddcdatabases.core.constants import MYSQL_SSL_MODES expected_modes = {"DISABLED", "PREFERRED", "REQUIRED", "VERIFY_CA", "VERIFY_IDENTITY"} assert MYSQL_SSL_MODES == expected_modes diff --git a/tests/unit/mariadb/test_mariadb_persistent.py b/tests/unit/mariadb/test_mariadb_persistent.py index 4019479..874853f 100644 --- a/tests/unit/mariadb/test_mariadb_persistent.py +++ b/tests/unit/mariadb/test_mariadb_persistent.py @@ -5,8 +5,8 @@ """ # noinspection PyProtectedMember -from ddcDatabases import MariaDBPersistent -from ddcDatabases.core.persistent import ( +from ddcdatabases import MariaDBPersistent +from ddcdatabases.core.persistent import ( MySQLPersistent, PersistentSQLAlchemyAsyncConnection, PersistentSQLAlchemyConnection, diff --git a/tests/unit/mariadb/test_mariadb_ssl.py b/tests/unit/mariadb/test_mariadb_ssl.py index 9be0165..dd98f5f 100644 --- a/tests/unit/mariadb/test_mariadb_ssl.py +++ b/tests/unit/mariadb/test_mariadb_ssl.py @@ -12,14 +12,14 @@ class TestMariaDBSSLConfig: def test_mariadb_ssl_config_is_mysql_alias(self): """Test that MariaDBSSLConfig is an alias for MySQLSSLConfig.""" - from ddcDatabases import MariaDBSSLConfig - from ddcDatabases.mysql import MySQLSSLConfig + from ddcdatabases import MariaDBSSLConfig + from ddcdatabases.mysql import MySQLSSLConfig assert MariaDBSSLConfig is MySQLSSLConfig def test_valid_ssl_modes(self): """Test all valid MariaDB SSL modes.""" - from ddcDatabases import MariaDBSSLConfig + from ddcdatabases import MariaDBSSLConfig valid_modes = ["DISABLED", "PREFERRED", "REQUIRED", "VERIFY_CA", "VERIFY_IDENTITY"] for mode in valid_modes: @@ -28,14 +28,14 @@ def test_valid_ssl_modes(self): def test_invalid_ssl_mode_raises_error(self): """Test that invalid SSL mode raises ValueError.""" - from ddcDatabases import MariaDBSSLConfig + from ddcdatabases import MariaDBSSLConfig with pytest.raises(ValueError, match="ssl_mode must be one of"): MariaDBSSLConfig(ssl_mode="invalid_mode") def test_ssl_config_with_all_paths(self): """Test SSL config with all certificate paths.""" - from ddcDatabases import MariaDBSSLConfig + from ddcdatabases import MariaDBSSLConfig config = MariaDBSSLConfig( ssl_mode="VERIFY_IDENTITY", @@ -50,7 +50,7 @@ def test_ssl_config_with_all_paths(self): def test_ssl_config_immutability(self): """Test that SSL config is immutable (frozen).""" - from ddcDatabases import MariaDBSSLConfig + from ddcdatabases import MariaDBSSLConfig config = MariaDBSSLConfig(ssl_mode="REQUIRED") with pytest.raises(AttributeError): @@ -58,7 +58,7 @@ def test_ssl_config_immutability(self): def test_default_values_are_none(self): """Test MariaDB SSL config default values are None.""" - from ddcDatabases import MariaDBSSLConfig + from ddcdatabases import MariaDBSSLConfig config = MariaDBSSLConfig() assert config.ssl_mode is None diff --git a/tests/unit/mongodb/test_mongodb.py b/tests/unit/mongodb/test_mongodb.py index f7001e7..8230ac2 100644 --- a/tests/unit/mongodb/test_mongodb.py +++ b/tests/unit/mongodb/test_mongodb.py @@ -1,5 +1,5 @@ import pytest -from ddcDatabases.mongodb import ( +from ddcdatabases.mongodb import ( MongoDB, MongoDBConnectionConfig, MongoDBQueryConfig, @@ -12,13 +12,13 @@ @pytest.fixture(autouse=True) def setup_mongodb_test_env(): """Setup clean test environment for MongoDB tests to prevent interference""" - import ddcDatabases.mongodb + import ddcdatabases.mongodb import logging # Store original state original_disabled_level = logging.root.manager.disable # noinspection PyProtectedMember - logger = ddcDatabases.mongodb._logger + logger = ddcdatabases.mongodb._logger original_handlers = logger.handlers[:] original_level = logger.level original_propagate = logger.propagate @@ -111,7 +111,7 @@ def test_init_with_settings(self): assert mongodb._query_config.limit == 0 assert not mongodb.is_connected - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_init_with_parameters(self, mock_get_settings): """Test MongoDB initialization with override parameters""" mock_settings = self._create_mock_settings( @@ -143,8 +143,8 @@ def test_init_with_parameters(self, mock_get_settings): assert mongodb._query_config.batch_size == 500 assert mongodb._query_config.limit == 100 - @patch("ddcDatabases.mongodb.get_mongodb_settings") - @patch("ddcDatabases.mongodb.MongoClient") + @patch("ddcdatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.MongoClient") def test_init_with_query_parameter(self, mock_mongo_client, mock_get_settings): """Test MongoDB initialization with query parameter and cursor creation""" mock_settings = self._create_mock_settings() @@ -390,8 +390,8 @@ def patched_exit(mongodb_self, exc_type, exc_val, exc_tb): assert not mongodb.is_connected mongodb.client.close.assert_called_once() - @patch("ddcDatabases.mongodb.get_mongodb_settings") - @patch("ddcDatabases.mongodb.MongoClient") + @patch("ddcdatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.MongoClient") def test_enter_method_client_close_condition(self, _mock_mongo_client, mock_get_settings): """Test __enter__ method client.close() condition - Line 47""" mock_settings = self._create_mock_settings() @@ -410,7 +410,7 @@ def test_enter_method_client_close_condition(self, _mock_mongo_client, mock_get_ # Verify client.close() was called mock_client.close.assert_called_once() - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_exit_method_with_none_client(self, mock_get_settings): """Test __exit__ method when client is None - Lines 51-53""" mock_settings = self._create_mock_settings() @@ -432,14 +432,14 @@ def test_missing_collection_runtime_error(self): with pytest.raises(ValueError, match="MongoDB collection name is required"): MongoDB(query_config=MongoDBQueryConfig(query={"test": "value"})) # collection is None - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_empty_query_defaults_to_empty_dict(self, mock_get_settings): """Test that missing query defaults to {} for fetching entire collection""" mock_get_settings.return_value = self._create_mock_settings() mongodb = MongoDB(collection="test_collection") # query is None assert mongodb._query_config.query == {} # Should default to empty dict - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_create_cursor_with_empty_query(self, mock_get_settings): """Test _create_cursor works with empty query (fetch entire collection)""" mock_settings = self._create_mock_settings() @@ -466,7 +466,7 @@ def test_create_cursor_with_empty_query(self, mock_get_settings): mock_cursor.batch_size.assert_called_once_with(2865) assert result == mock_cursor - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_exit_with_cursor_cleanup(self, mock_get_settings): """Test __exit__ method cleans up cursor_ref""" mock_settings = self._create_mock_settings() @@ -492,7 +492,7 @@ def test_exit_with_cursor_cleanup(self, mock_get_settings): mock_client.close.assert_called_once() assert mongodb.is_connected is False - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_exit_without_cursor_ref(self, mock_get_settings): """Test __exit__ method when cursor_ref is None""" mock_settings = self._create_mock_settings() @@ -511,7 +511,7 @@ def test_exit_without_cursor_ref(self, mock_get_settings): mock_client.close.assert_called_once() assert mongodb.is_connected is False - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_create_cursor_with_sorting(self, mock_get_settings): """Test _create_cursor with sorting parameters""" mock_settings = self._create_mock_settings() @@ -552,7 +552,7 @@ def test_create_cursor_with_sorting(self, mock_get_settings): # Return value should be the sorted cursor assert result is mock_sorted_cursor - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_create_cursor_with_ascending_sort(self, mock_get_settings): """Test _create_cursor with ascending sort""" mock_settings = self._create_mock_settings() @@ -579,7 +579,7 @@ def test_create_cursor_with_ascending_sort(self, mock_get_settings): mock_collection.create_index.assert_called_once_with([("name", ASCENDING)]) - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_create_cursor_with_none_query(self, mock_get_settings): """Test _create_cursor with None query (should default to empty dict)""" mock_settings = self._create_mock_settings() @@ -604,7 +604,7 @@ def test_create_cursor_with_none_query(self, mock_get_settings): # Verify empty dict was used for query mock_collection.find.assert_called_once_with({}, batch_size=2865, limit=0) - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_init_with_sort_parameters(self, mock_get_settings): """Test MongoDB initialization with sort parameters""" mock_settings = self._create_mock_settings() @@ -629,7 +629,7 @@ def test_init_with_sort_parameters(self, mock_get_settings): assert mongodb._query_config.batch_size == 1000 assert mongodb._query_config.limit == 50 - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_create_cursor_with_sort_column_only(self, mock_get_settings): """Test _create_cursor with only sort_column (should default to ascending)""" mock_settings = self._create_mock_settings() @@ -658,7 +658,7 @@ def test_create_cursor_with_sort_column_only(self, mock_get_settings): mock_collection.create_index.assert_called_once_with([("created_at", ASCENDING)]) mock_cursor.sort.assert_called_once_with("created_at", ASCENDING) - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_create_cursor_with_id_field_sorting(self, mock_get_settings): """Test _create_cursor with _id field sorting (should not create index but still sort)""" mock_settings = self._create_mock_settings() @@ -707,7 +707,7 @@ def test_enter_method_structure(self): """Test __enter__ method structural elements for coverage""" mock_settings = self._create_mock_settings() - with patch("ddcDatabases.mongodb.get_mongodb_settings", return_value=mock_settings): + with patch("ddcdatabases.mongodb.get_mongodb_settings", return_value=mock_settings): mongodb = MongoDB(collection="test_collection", query_config=MongoDBQueryConfig(query={"test": "value"})) # Test connection URL creation (line 41) @@ -724,7 +724,7 @@ def test_enter_exception_handling_structure(self): """Test __enter__ exception handling structure for coverage""" mock_settings = self._create_mock_settings() - with patch("ddcDatabases.mongodb.get_mongodb_settings", return_value=mock_settings): + with patch("ddcdatabases.mongodb.get_mongodb_settings", return_value=mock_settings): mongodb = MongoDB(collection="test_collection", query_config=MongoDBQueryConfig(query={"test": "value"})) # Test the exception handling logic structure @@ -743,7 +743,7 @@ def test_client_assignment_and_connection_flag(self): """Test client assignment and is_connected flag - Lines 42, 44, 45""" mock_settings = self._create_mock_settings() - with patch("ddcDatabases.mongodb.get_mongodb_settings", return_value=mock_settings): + with patch("ddcdatabases.mongodb.get_mongodb_settings", return_value=mock_settings): mongodb = MongoDB(collection="test_collection", query_config=MongoDBQueryConfig(query={"test": "value"})) # Test the assignment logic that happens in __enter__ @@ -767,7 +767,7 @@ def test_test_connection_raises_connection_error_on_failure(self): mock_settings = self._create_mock_settings() - with patch("ddcDatabases.mongodb.get_mongodb_settings", return_value=mock_settings): + with patch("ddcdatabases.mongodb.get_mongodb_settings", return_value=mock_settings): mongodb = MongoDB(collection="test_collection", query_config=MongoDBQueryConfig(query={"test": "value"})) # Mock client with failing ping @@ -782,7 +782,7 @@ def test_create_cursor_ascending_sort_with_limit(self): """Test _create_cursor with ascending sort order and custom limit.""" mock_settings = self._create_mock_settings(batch_size=100, limit=10) - with patch("ddcDatabases.mongodb.get_mongodb_settings", return_value=mock_settings): + with patch("ddcdatabases.mongodb.get_mongodb_settings", return_value=mock_settings): mongodb = MongoDB(collection="test_collection") # Create proper nested mock structure @@ -814,7 +814,7 @@ def test_create_cursor_with_default_sort_order(self): """Test _create_cursor with no explicit sort order defaults to ascending.""" mock_settings = self._create_mock_settings(batch_size=100, limit=10) - with patch("ddcDatabases.mongodb.get_mongodb_settings", return_value=mock_settings): + with patch("ddcdatabases.mongodb.get_mongodb_settings", return_value=mock_settings): mongodb = MongoDB(collection="test_collection") # Create proper nested mock structure @@ -841,7 +841,7 @@ def test_create_cursor_with_default_sort_order(self): call_args = mock_cursor.sort.call_args assert call_args[0][1] == ASCENDING - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_enter_with_connection_error_calls_sys_exit(self, mock_get_settings): """Test that __enter__ calls sys.exit when connection fails.""" from pymongo.errors import PyMongoError @@ -851,8 +851,8 @@ def test_enter_with_connection_error_calls_sys_exit(self, mock_get_settings): mock_client = MagicMock() mock_client.admin.command.side_effect = PyMongoError("Connection failed") - with patch("ddcDatabases.mongodb.MongoClient", return_value=mock_client): - from ddcDatabases.mongodb import MongoDBConnectionRetryConfig + with patch("ddcdatabases.mongodb.MongoClient", return_value=mock_client): + from ddcdatabases.mongodb import MongoDBConnectionRetryConfig mongodb = MongoDB( collection="test_collection", @@ -865,7 +865,7 @@ def test_enter_with_connection_error_calls_sys_exit(self, mock_get_settings): assert exc_info.value.code == 1 - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_tls_enabled(self, mock_get_settings): """Test MongoDB TLS configuration""" mock_settings = self._create_mock_settings() @@ -890,7 +890,7 @@ def test_tls_enabled(self, mock_get_settings): assert mongodb._tls_config.tls_cert_key_path == "/path/to/cert.pem" assert mongodb._tls_config.tls_allow_invalid_certificates - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_tls_disabled(self, mock_get_settings): """Test MongoDB with TLS disabled""" mock_settings = self._create_mock_settings() @@ -907,8 +907,8 @@ def test_tls_disabled(self, mock_get_settings): assert mongodb._tls_config.tls_cert_key_path is None assert not mongodb._tls_config.tls_allow_invalid_certificates - @patch("ddcDatabases.mongodb.get_mongodb_settings") - @patch("ddcDatabases.mongodb.MongoClient") + @patch("ddcdatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.MongoClient") def test_tls_connection_url(self, mock_mongo_client, mock_get_settings): """Test MongoDB TLS parameters are appended to connection URL""" mock_settings = self._create_mock_settings() @@ -948,7 +948,7 @@ def test_tls_connection_url(self, mock_mongo_client, mock_get_settings): assert _connection_url == expected_url - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_get_connection_info(self, mock_get_settings): """Test get_connection_info returns connection config""" mock_get_settings.return_value = self._create_mock_settings() @@ -961,7 +961,7 @@ def test_get_connection_info(self, mock_get_settings): assert conn_info.host == "localhost" assert conn_info.collection == "test_collection" - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_get_query_info(self, mock_get_settings): """Test get_query_info returns query config""" mock_get_settings.return_value = self._create_mock_settings() @@ -977,7 +977,7 @@ def test_get_query_info(self, mock_get_settings): assert query_info.query == {"status": "active"} assert query_info.batch_size == 500 - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_get_operation_retry_info(self, mock_get_settings): """Test get_operation_retry_info returns operation retry config""" mock_get_settings.return_value = self._create_mock_settings() @@ -993,7 +993,7 @@ def test_get_operation_retry_info(self, mock_get_settings): assert hasattr(op_retry_info, "max_retry_delay") assert hasattr(op_retry_info, "jitter") - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_get_tls_info(self, mock_get_settings): """Test get_tls_info returns TLS config""" mock_get_settings.return_value = self._create_mock_settings() @@ -1009,7 +1009,7 @@ def test_get_tls_info(self, mock_get_settings): assert tls_info.tls_enabled assert tls_info.tls_ca_cert_path == "/path/to/ca.pem" - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_build_connection_url_with_tls(self, mock_get_settings): """Test _build_connection_url with TLS options""" mock_get_settings.return_value = self._create_mock_settings() @@ -1030,7 +1030,7 @@ def test_build_connection_url_with_tls(self, mock_get_settings): assert "&tlsCertificateKeyFile=/path/to/cert.pem" in url assert "&tlsAllowInvalidCertificates=true" in url - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_build_connection_url_without_tls(self, mock_get_settings): """Test _build_connection_url without TLS""" mock_get_settings.return_value = self._create_mock_settings() @@ -1041,7 +1041,7 @@ def test_build_connection_url_without_tls(self, mock_get_settings): assert "?tls=true" not in url assert "mongodb://admin:admin@localhost/admin" == url - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_test_connection_success(self, mock_get_settings): """Test _test_connection logs success message""" mock_get_settings.return_value = self._create_mock_settings() @@ -1059,7 +1059,7 @@ def test_test_connection_success(self, mock_get_settings): assert "Connected to" in str(mongodb.logger.info.call_args) @pytest.mark.asyncio - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") async def test_async_aenter_success(self, mock_get_settings): """Test async __aenter__ method structure and _create_cursor_async""" mock_get_settings.return_value = self._create_mock_settings() @@ -1087,7 +1087,7 @@ async def test_async_aenter_success(self, mock_get_settings): mock_collection.find.assert_called_once() @pytest.mark.asyncio - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") async def test_async_aexit_cleanup(self, mock_get_settings): """Test async __aexit__ cleans up resources""" mock_get_settings.return_value = self._create_mock_settings() @@ -1113,7 +1113,7 @@ async def mock_close(): mock_client.close.assert_called_once() @pytest.mark.asyncio - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") async def test_async_aexit_without_cursor(self, mock_get_settings): """Test async __aexit__ when cursor is None""" mock_get_settings.return_value = self._create_mock_settings() @@ -1131,7 +1131,7 @@ async def test_async_aexit_without_cursor(self, mock_get_settings): mock_client.close.assert_called_once() @pytest.mark.asyncio - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") async def test_async_test_connection_success(self, mock_get_settings): """Test _test_connection_async logs success message""" mock_get_settings.return_value = self._create_mock_settings() @@ -1152,7 +1152,7 @@ async def mock_command(cmd): assert "Async connected to" in str(mongodb.logger.info.call_args) @pytest.mark.asyncio - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") async def test_async_test_connection_failure(self, mock_get_settings): """Test _test_connection_async raises ConnectionError on failure""" from pymongo.errors import PyMongoError @@ -1171,7 +1171,7 @@ async def mock_command(_cmd): with pytest.raises(ConnectionError, match="Async connection to MongoDB failed"): await mongodb._test_connection_async() - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_create_cursor_async(self, mock_get_settings): """Test _create_cursor_async method""" mock_get_settings.return_value = self._create_mock_settings(batch_size=100, limit=10) @@ -1200,7 +1200,7 @@ def test_create_cursor_async(self, mock_get_settings): mock_cursor.batch_size.assert_called_once_with(100) assert cursor is mock_cursor - @patch("ddcDatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.get_mongodb_settings") def test_create_cursor_async_with_sorting(self, mock_get_settings): """Test _create_cursor_async with sorting""" from pymongo import DESCENDING @@ -1227,11 +1227,11 @@ def test_create_cursor_async_with_sorting(self, mock_get_settings): mock_cursor.sort.assert_called_once_with("created_at", DESCENDING) @pytest.mark.asyncio - @patch("ddcDatabases.mongodb.get_mongodb_settings") - @patch("ddcDatabases.mongodb.AsyncIOMotorClient") + @patch("ddcdatabases.mongodb.get_mongodb_settings") + @patch("ddcdatabases.mongodb.AsyncIOMotorClient") async def test_async_aenter_connection_error(self, mock_async_client, mock_get_settings): """Test async __aenter__ handles connection error""" - from ddcDatabases.mongodb import MongoDBConnectionRetryConfig + from ddcdatabases.mongodb import MongoDBConnectionRetryConfig from pymongo.errors import PyMongoError mock_get_settings.return_value = self._create_mock_settings() diff --git a/tests/unit/mongodb/test_mongodb_persistent.py b/tests/unit/mongodb/test_mongodb_persistent.py index ef1509c..a0719b3 100644 --- a/tests/unit/mongodb/test_mongodb_persistent.py +++ b/tests/unit/mongodb/test_mongodb_persistent.py @@ -1,7 +1,7 @@ """Tests for MongoDB persistent connections.""" # noinspection PyProtectedMember -from ddcDatabases.core.persistent import ( +from ddcdatabases.core.persistent import ( MongoDBPersistent, PersistentConnectionConfig, PersistentMongoDBConnection, diff --git a/tests/unit/mongodb/test_mongodb_tls.py b/tests/unit/mongodb/test_mongodb_tls.py index 862c14d..eebaaf9 100644 --- a/tests/unit/mongodb/test_mongodb_tls.py +++ b/tests/unit/mongodb/test_mongodb_tls.py @@ -8,7 +8,7 @@ class TestMongoDBTLSConfig: def test_default_values_are_none(self): """Test MongoDB TLS config default values are None.""" - from ddcDatabases.mongodb import MongoDBTLSConfig + from ddcdatabases.mongodb import MongoDBTLSConfig config = MongoDBTLSConfig() assert config.tls_enabled is None @@ -18,14 +18,14 @@ def test_default_values_are_none(self): def test_tls_enabled(self): """Test MongoDB TLS enabled configuration.""" - from ddcDatabases.mongodb import MongoDBTLSConfig + from ddcdatabases.mongodb import MongoDBTLSConfig config = MongoDBTLSConfig(tls_enabled=True) assert config.tls_enabled is True def test_tls_config_with_all_paths(self): """Test MongoDB TLS config with all certificate paths.""" - from ddcDatabases.mongodb import MongoDBTLSConfig + from ddcdatabases.mongodb import MongoDBTLSConfig config = MongoDBTLSConfig( tls_enabled=True, @@ -40,7 +40,7 @@ def test_tls_config_with_all_paths(self): def test_tls_config_immutability(self): """Test that TLS config is immutable (frozen).""" - from ddcDatabases.mongodb import MongoDBTLSConfig + from ddcdatabases.mongodb import MongoDBTLSConfig config = MongoDBTLSConfig(tls_enabled=True) with pytest.raises(AttributeError): @@ -48,14 +48,14 @@ def test_tls_config_immutability(self): def test_tls_disabled(self): """Test MongoDB TLS disabled configuration.""" - from ddcDatabases.mongodb import MongoDBTLSConfig + from ddcdatabases.mongodb import MongoDBTLSConfig config = MongoDBTLSConfig(tls_enabled=False) assert config.tls_enabled is False def test_tls_allow_invalid_certificates(self): """Test MongoDB TLS allow invalid certificates.""" - from ddcDatabases.mongodb import MongoDBTLSConfig + from ddcdatabases.mongodb import MongoDBTLSConfig config = MongoDBTLSConfig( tls_enabled=True, @@ -65,7 +65,7 @@ def test_tls_allow_invalid_certificates(self): def test_tls_ca_cert_path_only(self): """Test MongoDB TLS config with only CA cert path.""" - from ddcDatabases.mongodb import MongoDBTLSConfig + from ddcdatabases.mongodb import MongoDBTLSConfig config = MongoDBTLSConfig( tls_enabled=True, @@ -76,7 +76,7 @@ def test_tls_ca_cert_path_only(self): def test_tls_cert_key_path_only(self): """Test MongoDB TLS config with only cert key path.""" - from ddcDatabases.mongodb import MongoDBTLSConfig + from ddcdatabases.mongodb import MongoDBTLSConfig config = MongoDBTLSConfig( tls_enabled=True, diff --git a/tests/unit/mssql/test_mssql.py b/tests/unit/mssql/test_mssql.py index 5ff7204..6b488ef 100644 --- a/tests/unit/mssql/test_mssql.py +++ b/tests/unit/mssql/test_mssql.py @@ -1,5 +1,5 @@ import pytest -from ddcDatabases.mssql import MSSQL, MSSQLConnectionConfig, MSSQLPoolConfig, MSSQLSessionConfig, MSSQLSSLConfig +from ddcdatabases.mssql import MSSQL, MSSQLConnectionConfig, MSSQLPoolConfig, MSSQLSessionConfig, MSSQLSSLConfig from unittest.mock import MagicMock, patch @@ -50,7 +50,7 @@ def _create_mock_settings(self, **overrides): # already covered by the credential validation tests that are working. # Core functionality (credential validation) is tested and working. - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_init_with_parameters(self, mock_get_settings): """Test MSSQL initialization with override parameters""" mock_settings = self._create_mock_settings( @@ -173,7 +173,7 @@ def patched_init(mssql_self, *args, **kwargs): # This test was testing URL schema parameters which is an edge case # Core functionality is covered by other tests - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_custom_odbcdriver_version(self, mock_get_settings): """Test custom ODBC driver version""" mock_settings = self._create_mock_settings(odbcdriver_version=18) @@ -185,7 +185,7 @@ def test_custom_odbcdriver_version(self, mock_get_settings): assert mssql._connection_config.odbcdriver_version == 18 assert mssql.connection_url["query"]["driver"] == "ODBC Driver 18 for SQL Server" - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_extra_engine_args(self, mock_get_settings): """Test extra engine arguments are properly included""" mock_settings = self._create_mock_settings() @@ -204,7 +204,7 @@ def test_extra_engine_args(self, mock_get_settings): assert mssql.engine_args["max_overflow"] == 50 assert not mssql.engine_args["echo"] - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_autoflush_and_expire_on_commit(self, mock_get_settings): """Test MSSQL autoflush and expire_on_commit parameters""" mock_settings = self._create_mock_settings() @@ -215,7 +215,7 @@ def test_autoflush_and_expire_on_commit(self, mock_get_settings): assert not mssql._session_config.autoflush assert not mssql._session_config.expire_on_commit - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_autocommit_parameter(self, mock_get_settings): """Test MSSQL autocommit parameter""" mock_settings = self._create_mock_settings(odbcdriver_version=18) @@ -226,7 +226,7 @@ def test_autocommit_parameter(self, mock_get_settings): assert mssql._session_config.autocommit assert mssql.engine_args["connect_args"]["autocommit"] - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_connection_timeout_parameter(self, mock_get_settings): """Test MSSQL connection_timeout parameter""" mock_settings = self._create_mock_settings(odbcdriver_version=18) @@ -238,7 +238,7 @@ def test_connection_timeout_parameter(self, mock_get_settings): assert mssql.engine_args["connect_args"]["timeout"] == 60 assert mssql.engine_args["connect_args"]["login_timeout"] == 60 - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_pool_recycle_parameter(self, mock_get_settings): """Test MSSQL pool_recycle parameter""" mock_settings = self._create_mock_settings(odbcdriver_version=18) @@ -249,7 +249,7 @@ def test_pool_recycle_parameter(self, mock_get_settings): assert mssql._pool_config.pool_recycle == 7200 assert mssql.engine_args["pool_recycle"] == 7200 - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_all_parameters_defaults(self, mock_get_settings): """Test MSSQL parameters use settings defaults""" mock_settings = self._create_mock_settings(autoflush=False, expire_on_commit=False, odbcdriver_version=18) @@ -266,7 +266,7 @@ def test_all_parameters_defaults(self, mock_get_settings): assert mssql.engine_args["connect_args"]["timeout"] == 30 assert mssql.engine_args["pool_recycle"] == 3600 - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_test_connection_sync_url_creation(self, mock_get_settings): """Test _test_connection_sync URL creation logic - Lines 73-77""" mock_settings = self._create_mock_settings() @@ -299,7 +299,7 @@ def test_test_connection_sync_url_creation(self, mock_get_settings): assert _connection_url.port == 1433 assert _connection_url.database == "master" - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_test_connection_async_url_creation(self, mock_get_settings): """Test _test_connection_async URL creation logic - Lines 87-91""" mock_settings = self._create_mock_settings() @@ -332,7 +332,7 @@ def test_test_connection_async_url_creation(self, mock_get_settings): assert _connection_url.port == 1433 assert _connection_url.database == "master" - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_connection_url_modification_sync(self, mock_get_settings): """Test that connection_url is properly copied (not modified) in _test_connection_sync""" mock_settings = self._create_mock_settings() @@ -355,7 +355,7 @@ def test_connection_url_modification_sync(self, mock_get_settings): assert "password" in mssql.connection_url assert "query" in mssql.connection_url - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_connection_url_modification_async(self, mock_get_settings): """Test that connection_url is properly copied (not modified) in _test_connection_async""" mock_settings = self._create_mock_settings(odbcdriver_version=18) @@ -382,7 +382,7 @@ def test_connection_url_modification_async(self, mock_get_settings): assert "password" in mssql.connection_url assert "query" in mssql.connection_url - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_ssl_encrypt_enabled(self, mock_get_settings): """Test MSSQL SSL encrypt enabled""" mock_settings = self._create_mock_settings(odbcdriver_version=18) @@ -395,7 +395,7 @@ def test_ssl_encrypt_enabled(self, mock_get_settings): assert mssql.connection_url["query"]["Encrypt"] == "yes" assert mssql.connection_url["query"]["TrustServerCertificate"] == "no" - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_ssl_encrypt_disabled(self, mock_get_settings): """Test MSSQL SSL encrypt disabled""" mock_settings = self._create_mock_settings(odbcdriver_version=18) @@ -408,7 +408,7 @@ def test_ssl_encrypt_disabled(self, mock_get_settings): assert mssql.connection_url["query"]["Encrypt"] == "no" assert mssql.connection_url["query"]["TrustServerCertificate"] == "yes" - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_get_ssl_info(self, mock_get_settings): """Test get_ssl_info() returns the immutable SSL configuration""" mock_settings = self._create_mock_settings(odbcdriver_version=18) @@ -427,7 +427,7 @@ def test_get_ssl_info(self, mock_get_settings): assert not ssl_info.ssl_trust_server_certificate assert ssl_info.ssl_ca_cert_path == "/path/to/ca.pem" - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_get_connection_info(self, mock_get_settings): """Test get_connection_info returns connection config""" mock_get_settings.return_value = self._create_mock_settings() @@ -440,7 +440,7 @@ def test_get_connection_info(self, mock_get_settings): assert conn_info.host == "localhost" assert conn_info.port == 1433 - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_get_pool_info(self, mock_get_settings): """Test get_pool_info returns pool config""" mock_get_settings.return_value = self._create_mock_settings() @@ -453,7 +453,7 @@ def test_get_pool_info(self, mock_get_settings): assert pool_info.pool_size == 15 assert pool_info.max_overflow == 30 - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_get_session_info(self, mock_get_settings): """Test get_session_info returns session config""" mock_get_settings.return_value = self._create_mock_settings() @@ -466,7 +466,7 @@ def test_get_session_info(self, mock_get_settings): assert session_info.echo assert not session_info.autoflush - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_get_operation_retry_info(self, mock_get_settings): """Test get_operation_retry_info returns operation retry config""" mock_get_settings.return_value = self._create_mock_settings() diff --git a/tests/unit/mssql/test_mssql_persistent.py b/tests/unit/mssql/test_mssql_persistent.py index fe188be..6ffcdf2 100644 --- a/tests/unit/mssql/test_mssql_persistent.py +++ b/tests/unit/mssql/test_mssql_persistent.py @@ -1,7 +1,7 @@ """Tests for MSSQL persistent connections.""" # noinspection PyProtectedMember -from ddcDatabases.core.persistent import ( +from ddcdatabases.core.persistent import ( MSSQLPersistent, PersistentSQLAlchemyAsyncConnection, PersistentSQLAlchemyConnection, diff --git a/tests/unit/mssql/test_mssql_ssl.py b/tests/unit/mssql/test_mssql_ssl.py index cf39b30..723eb18 100644 --- a/tests/unit/mssql/test_mssql_ssl.py +++ b/tests/unit/mssql/test_mssql_ssl.py @@ -10,7 +10,7 @@ class TestMSSQLSSLConfig: def test_default_values_are_none(self): """Test MSSQL SSL config default values are None.""" - from ddcDatabases.mssql import MSSQLSSLConfig + from ddcdatabases.mssql import MSSQLSSLConfig config = MSSQLSSLConfig() assert config.ssl_encrypt is None @@ -19,7 +19,7 @@ def test_default_values_are_none(self): def test_ssl_encrypt_enabled(self): """Test MSSQL SSL encryption enabled.""" - from ddcDatabases.mssql import MSSQLSSLConfig + from ddcdatabases.mssql import MSSQLSSLConfig config = MSSQLSSLConfig(ssl_encrypt=True, ssl_trust_server_certificate=False) assert config.ssl_encrypt is True @@ -27,7 +27,7 @@ def test_ssl_encrypt_enabled(self): def test_ssl_config_with_ca_cert(self): """Test MSSQL SSL config with CA certificate.""" - from ddcDatabases.mssql import MSSQLSSLConfig + from ddcdatabases.mssql import MSSQLSSLConfig config = MSSQLSSLConfig( ssl_encrypt=True, @@ -38,7 +38,7 @@ def test_ssl_config_with_ca_cert(self): def test_ssl_config_immutability(self): """Test that SSL config is immutable (frozen).""" - from ddcDatabases.mssql import MSSQLSSLConfig + from ddcdatabases.mssql import MSSQLSSLConfig config = MSSQLSSLConfig(ssl_encrypt=True) with pytest.raises(AttributeError): @@ -46,7 +46,7 @@ def test_ssl_config_immutability(self): def test_ssl_encrypt_disabled(self): """Test MSSQL SSL encryption disabled.""" - from ddcDatabases.mssql import MSSQLSSLConfig + from ddcdatabases.mssql import MSSQLSSLConfig config = MSSQLSSLConfig(ssl_encrypt=False, ssl_trust_server_certificate=True) assert config.ssl_encrypt is False @@ -54,7 +54,7 @@ def test_ssl_encrypt_disabled(self): def test_trust_server_certificate_only(self): """Test MSSQL SSL config with trust server certificate only.""" - from ddcDatabases.mssql import MSSQLSSLConfig + from ddcdatabases.mssql import MSSQLSSLConfig config = MSSQLSSLConfig(ssl_trust_server_certificate=True) assert config.ssl_trust_server_certificate is True @@ -62,7 +62,7 @@ def test_trust_server_certificate_only(self): def test_ssl_encrypt_with_trust_cert(self): """Test MSSQL SSL encrypt with trust certificate combination.""" - from ddcDatabases.mssql import MSSQLSSLConfig + from ddcdatabases.mssql import MSSQLSSLConfig config = MSSQLSSLConfig(ssl_encrypt=True, ssl_trust_server_certificate=True) assert config.ssl_encrypt is True @@ -75,18 +75,18 @@ class TestMSSQLSSLConnectionURL: # noinspection PyMethodMayBeStatic def setup_method(self): """Clear cache before each test.""" - from ddcDatabases.core.settings import get_mssql_settings + from ddcdatabases.core.settings import get_mssql_settings get_mssql_settings.cache_clear() - import ddcDatabases.core.settings + import ddcdatabases.core.settings - ddcDatabases.core.settings._dotenv_loaded = False + ddcdatabases.core.settings._dotenv_loaded = False - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_ssl_ca_cert_path_in_connection_url(self, mock_get_settings): """Test that ssl_ca_cert_path is added to connection URL query as ServerCertificate.""" - from ddcDatabases.mssql import MSSQL, MSSQLSSLConfig + from ddcdatabases.mssql import MSSQL, MSSQLSSLConfig mock_settings = MagicMock() mock_settings.host = "localhost" @@ -132,10 +132,10 @@ def test_ssl_ca_cert_path_in_connection_url(self, mock_get_settings): assert mssql.connection_url["query"]["Encrypt"] == "yes" assert mssql.connection_url["query"]["TrustServerCertificate"] == "no" - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_ssl_no_ca_cert_path_no_server_certificate_key(self, mock_get_settings): """Test that ServerCertificate is not in query when ssl_ca_cert_path is None.""" - from ddcDatabases.mssql import MSSQL + from ddcdatabases.mssql import MSSQL mock_settings = MagicMock() mock_settings.host = "localhost" @@ -173,10 +173,10 @@ def test_ssl_no_ca_cert_path_no_server_certificate_key(self, mock_get_settings): assert "ServerCertificate" not in mssql.connection_url["query"] - @patch("ddcDatabases.mssql.get_mssql_settings") + @patch("ddcdatabases.mssql.get_mssql_settings") def test_ssl_ca_cert_path_falls_back_to_settings(self, mock_get_settings): """Test that ssl_ca_cert_path falls back to settings when not provided in ssl_config.""" - from ddcDatabases.mssql import MSSQL + from ddcdatabases.mssql import MSSQL mock_settings = MagicMock() mock_settings.host = "localhost" @@ -222,17 +222,17 @@ class TestMSSQLSSLEnvVars: # noinspection PyMethodMayBeStatic def setup_method(self): """Clear cache before each test.""" - from ddcDatabases.core.settings import get_mssql_settings + from ddcdatabases.core.settings import get_mssql_settings get_mssql_settings.cache_clear() - import ddcDatabases.core.settings + import ddcdatabases.core.settings - ddcDatabases.core.settings._dotenv_loaded = False + ddcdatabases.core.settings._dotenv_loaded = False def test_ssl_ca_cert_path_from_env_var(self): """Test that MSSQL_SSL_CA_CERT_PATH env var is read by settings.""" - from ddcDatabases.core.settings import MSSQLSettings + from ddcdatabases.core.settings import MSSQLSettings with patch.dict( os.environ, diff --git a/tests/unit/mysql/test_mysql.py b/tests/unit/mysql/test_mysql.py index 7e675b2..7fc5595 100644 --- a/tests/unit/mysql/test_mysql.py +++ b/tests/unit/mysql/test_mysql.py @@ -1,5 +1,5 @@ import pytest -from ddcDatabases.mysql import MySQL, MySQLConnectionConfig, MySQLPoolConfig, MySQLSessionConfig, MySQLSSLConfig +from ddcdatabases.mysql import MySQL, MySQLConnectionConfig, MySQLPoolConfig, MySQLSessionConfig, MySQLSSLConfig from unittest.mock import MagicMock, patch @@ -83,7 +83,7 @@ def patched_init(mysql_self, *args, **kwargs): with pytest.raises(RuntimeError, match="Missing username/password"): MySQL() - @patch("ddcDatabases.mysql.get_mysql_settings") + @patch("ddcdatabases.mysql.get_mysql_settings") def test_init_with_parameters(self, mock_get_settings): """Test MySQL initialization with override parameters""" mock_settings = self._create_mock_settings( @@ -107,7 +107,7 @@ def test_init_with_parameters(self, mock_get_settings): assert mysql.connection_url["password"] == "custompass" assert mysql._session_config.echo - @patch("ddcDatabases.mysql.get_mysql_settings") + @patch("ddcdatabases.mysql.get_mysql_settings") def test_minimal_init(self, mock_get_settings): """Test MySQL minimal initialization""" mock_settings = self._create_mock_settings() @@ -119,7 +119,7 @@ def test_minimal_init(self, mock_get_settings): assert mysql.connection_url["port"] == 3306 assert mysql.sync_driver == "mysql+mysqldb" - @patch("ddcDatabases.mysql.get_mysql_settings") + @patch("ddcdatabases.mysql.get_mysql_settings") def test_extra_engine_args(self, mock_get_settings): """Test MySQL with extra engine arguments""" mock_settings = self._create_mock_settings(database="dev") @@ -136,7 +136,7 @@ def test_extra_engine_args(self, mock_get_settings): # Test that default args are still present assert not mysql.engine_args["echo"] - @patch("ddcDatabases.mysql.get_mysql_settings") + @patch("ddcdatabases.mysql.get_mysql_settings") def test_autoflush_and_expire_on_commit(self, mock_get_settings): """Test MySQL autoflush and expire_on_commit parameters""" mock_settings = self._create_mock_settings(database="dev") @@ -147,7 +147,7 @@ def test_autoflush_and_expire_on_commit(self, mock_get_settings): assert not mysql._session_config.autoflush assert not mysql._session_config.expire_on_commit - @patch("ddcDatabases.mysql.get_mysql_settings") + @patch("ddcdatabases.mysql.get_mysql_settings") def test_pool_size_parameter(self, mock_get_settings): """Test MySQL pool_size parameter""" mock_settings = self._create_mock_settings(database="dev", pool_size=10) @@ -158,7 +158,7 @@ def test_pool_size_parameter(self, mock_get_settings): assert mysql._pool_config.pool_size == 15 assert mysql.engine_args["pool_size"] == 15 - @patch("ddcDatabases.mysql.get_mysql_settings") + @patch("ddcdatabases.mysql.get_mysql_settings") def test_max_overflow_parameter(self, mock_get_settings): """Test MySQL max_overflow parameter""" mock_settings = self._create_mock_settings(database="dev", max_overflow=20) @@ -169,7 +169,7 @@ def test_max_overflow_parameter(self, mock_get_settings): assert mysql._pool_config.max_overflow == 30 assert mysql.engine_args["max_overflow"] == 30 - @patch("ddcDatabases.mysql.get_mysql_settings") + @patch("ddcdatabases.mysql.get_mysql_settings") def test_pool_parameters_defaults(self, mock_get_settings): """Test MySQL pool parameters use settings defaults""" mock_settings = self._create_mock_settings(database="dev", pool_size=10, max_overflow=20) @@ -182,7 +182,7 @@ def test_pool_parameters_defaults(self, mock_get_settings): assert mysql.engine_args["pool_size"] == 10 assert mysql.engine_args["max_overflow"] == 20 - @patch("ddcDatabases.mysql.get_mysql_settings") + @patch("ddcdatabases.mysql.get_mysql_settings") def test_ssl_enabled(self, mock_get_settings): """Test MySQL SSL configuration""" mock_settings = self._create_mock_settings(database="dev", ssl_mode="DISABLED") @@ -205,7 +205,7 @@ def test_ssl_enabled(self, mock_get_settings): assert ssl_dict["cert"] == "/path/to/client.pem" assert ssl_dict["key"] == "/path/to/client-key.pem" - @patch("ddcDatabases.mysql.get_mysql_settings") + @patch("ddcdatabases.mysql.get_mysql_settings") def test_ssl_disabled(self, mock_get_settings): """Test MySQL without SSL does not add ssl to connect_args""" mock_settings = self._create_mock_settings(database="dev", ssl_mode="DISABLED") @@ -216,7 +216,7 @@ def test_ssl_disabled(self, mock_get_settings): assert mysql._ssl_config.ssl_mode == "DISABLED" assert "ssl" not in mysql.engine_args["connect_args"] - @patch("ddcDatabases.mysql.get_mysql_settings") + @patch("ddcdatabases.mysql.get_mysql_settings") def test_get_connection_info(self, mock_get_settings): """Test get_connection_info returns connection config""" mock_get_settings.return_value = self._create_mock_settings() @@ -229,7 +229,7 @@ def test_get_connection_info(self, mock_get_settings): assert conn_info.host == "127.0.0.1" # localhost normalized to 127.0.0.1 assert conn_info.port == 3306 - @patch("ddcDatabases.mysql.get_mysql_settings") + @patch("ddcdatabases.mysql.get_mysql_settings") def test_get_pool_info(self, mock_get_settings): """Test get_pool_info returns pool config""" mock_get_settings.return_value = self._create_mock_settings() @@ -242,7 +242,7 @@ def test_get_pool_info(self, mock_get_settings): assert pool_info.pool_size == 20 assert pool_info.max_overflow == 40 - @patch("ddcDatabases.mysql.get_mysql_settings") + @patch("ddcdatabases.mysql.get_mysql_settings") def test_get_session_info(self, mock_get_settings): """Test get_session_info returns session config""" mock_get_settings.return_value = self._create_mock_settings() @@ -255,7 +255,7 @@ def test_get_session_info(self, mock_get_settings): assert session_info.echo assert not session_info.autoflush - @patch("ddcDatabases.mysql.get_mysql_settings") + @patch("ddcdatabases.mysql.get_mysql_settings") def test_get_connection_retry_info(self, mock_get_settings): """Test get_connection_retry_info returns connection retry config""" mock_get_settings.return_value = self._create_mock_settings() @@ -267,7 +267,7 @@ def test_get_connection_retry_info(self, mock_get_settings): assert hasattr(conn_retry_info, "enable_retry") assert hasattr(conn_retry_info, "max_retries") - @patch("ddcDatabases.mysql.get_mysql_settings") + @patch("ddcdatabases.mysql.get_mysql_settings") def test_get_operation_retry_info(self, mock_get_settings): """Test get_operation_retry_info returns operation retry config""" mock_get_settings.return_value = self._create_mock_settings() @@ -280,7 +280,7 @@ def test_get_operation_retry_info(self, mock_get_settings): assert hasattr(op_retry_info, "max_retries") assert hasattr(op_retry_info, "jitter") - @patch("ddcDatabases.mysql.get_mysql_settings") + @patch("ddcdatabases.mysql.get_mysql_settings") def test_get_ssl_info(self, mock_get_settings): """Test get_ssl_info returns SSL config""" mock_get_settings.return_value = self._create_mock_settings() diff --git a/tests/unit/mysql/test_mysql_persistent.py b/tests/unit/mysql/test_mysql_persistent.py index 5839df5..072d3d8 100644 --- a/tests/unit/mysql/test_mysql_persistent.py +++ b/tests/unit/mysql/test_mysql_persistent.py @@ -1,9 +1,9 @@ """Tests for MySQL persistent connections.""" -from ddcDatabases.core.configs import BaseRetryConfig as RetryConfig +from ddcdatabases.core.configs import BaseRetryConfig as RetryConfig # noinspection PyProtectedMember -from ddcDatabases.core.persistent import ( +from ddcdatabases.core.persistent import ( MySQLPersistent, PersistentConnectionConfig, PersistentSQLAlchemyAsyncConnection, diff --git a/tests/unit/mysql/test_mysql_ssl.py b/tests/unit/mysql/test_mysql_ssl.py index 19279d8..aef9a41 100644 --- a/tests/unit/mysql/test_mysql_ssl.py +++ b/tests/unit/mysql/test_mysql_ssl.py @@ -8,8 +8,8 @@ class TestMySQLSSLConfig: def test_valid_ssl_modes(self): """Test all valid MySQL SSL modes.""" - from ddcDatabases.core.constants import MYSQL_SSL_MODES - from ddcDatabases.mysql import MySQLSSLConfig + from ddcdatabases.core.constants import MYSQL_SSL_MODES + from ddcdatabases.mysql import MySQLSSLConfig for mode in MYSQL_SSL_MODES: config = MySQLSSLConfig(ssl_mode=mode) @@ -17,14 +17,14 @@ def test_valid_ssl_modes(self): def test_invalid_ssl_mode_raises_error(self): """Test that invalid SSL mode raises ValueError.""" - from ddcDatabases.mysql import MySQLSSLConfig + from ddcdatabases.mysql import MySQLSSLConfig with pytest.raises(ValueError, match="ssl_mode must be one of"): MySQLSSLConfig(ssl_mode="invalid_mode") def test_ssl_config_with_all_paths(self): """Test SSL config with all certificate paths.""" - from ddcDatabases.mysql import MySQLSSLConfig + from ddcdatabases.mysql import MySQLSSLConfig config = MySQLSSLConfig( ssl_mode="VERIFY_IDENTITY", @@ -39,7 +39,7 @@ def test_ssl_config_with_all_paths(self): def test_ssl_config_immutability(self): """Test that SSL config is immutable (frozen).""" - from ddcDatabases.mysql import MySQLSSLConfig + from ddcdatabases.mysql import MySQLSSLConfig config = MySQLSSLConfig(ssl_mode="REQUIRED") with pytest.raises(AttributeError): @@ -47,28 +47,28 @@ def test_ssl_config_immutability(self): def test_ssl_mode_none_by_default(self): """Test default SSL mode is None (not set).""" - from ddcDatabases.mysql import MySQLSSLConfig + from ddcdatabases.mysql import MySQLSSLConfig config = MySQLSSLConfig() assert config.ssl_mode is None def test_ssl_modes_case_insensitive_validation(self): """Test that MySQL SSL mode validation is case-insensitive.""" - from ddcDatabases.mysql import MySQLSSLConfig + from ddcdatabases.mysql import MySQLSSLConfig config = MySQLSSLConfig(ssl_mode="required") assert config.ssl_mode == "required" def test_preferred_mode(self): """Test PREFERRED SSL mode.""" - from ddcDatabases.mysql import MySQLSSLConfig + from ddcdatabases.mysql import MySQLSSLConfig config = MySQLSSLConfig(ssl_mode="PREFERRED") assert config.ssl_mode == "PREFERRED" def test_verify_ca_mode(self): """Test VERIFY_CA SSL mode.""" - from ddcDatabases.mysql import MySQLSSLConfig + from ddcdatabases.mysql import MySQLSSLConfig config = MySQLSSLConfig( ssl_mode="VERIFY_CA", @@ -78,7 +78,7 @@ def test_verify_ca_mode(self): def test_all_ssl_modes_are_valid(self): """Test that all documented SSL modes are accepted.""" - from ddcDatabases.mysql import MySQLSSLConfig + from ddcdatabases.mysql import MySQLSSLConfig valid_modes = ["DISABLED", "PREFERRED", "REQUIRED", "VERIFY_CA", "VERIFY_IDENTITY"] for mode in valid_modes: @@ -87,7 +87,7 @@ def test_all_ssl_modes_are_valid(self): def test_ssl_ca_cert_path_only(self): """Test SSL config with only CA certificate path.""" - from ddcDatabases.mysql import MySQLSSLConfig + from ddcdatabases.mysql import MySQLSSLConfig config = MySQLSSLConfig( ssl_mode="VERIFY_CA", diff --git a/tests/unit/oracle/test_oracle.py b/tests/unit/oracle/test_oracle.py index a6813d1..52d0ff9 100644 --- a/tests/unit/oracle/test_oracle.py +++ b/tests/unit/oracle/test_oracle.py @@ -1,6 +1,6 @@ import pytest -from ddcDatabases.core.base import ConnectionTester -from ddcDatabases.oracle import Oracle, OracleConnectionConfig, OraclePoolConfig, OracleSessionConfig, OracleSSLConfig +from ddcdatabases.core.base import ConnectionTester +from ddcdatabases.oracle import Oracle, OracleConnectionConfig, OraclePoolConfig, OracleSessionConfig, OracleSSLConfig from unittest.mock import AsyncMock, MagicMock, patch @@ -45,7 +45,7 @@ def _create_mock_settings(self, **overrides): setattr(mock_settings, key, value) return mock_settings - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_init_basic(self, mock_get_settings): """Test Oracle basic initialization""" mock_settings = self._create_mock_settings() @@ -104,7 +104,7 @@ def patched_init(oracle_self, *args, **kwargs): with pytest.raises(RuntimeError, match="Missing username/password"): Oracle() - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_init_with_parameters(self, mock_get_settings): """Test Oracle initialization with override parameters""" mock_settings = self._create_mock_settings( @@ -128,7 +128,7 @@ def test_init_with_parameters(self, mock_get_settings): assert oracle.connection_url["query"]["service_name"] == "customxe" assert oracle._session_config.echo - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_minimal_init(self, mock_get_settings): """Test Oracle minimal initialization""" mock_settings = self._create_mock_settings() @@ -169,7 +169,7 @@ async def test_test_connection_async_oracle(self): assert result mock_session.execute.assert_called_once() - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_extra_engine_args(self, mock_get_settings): """Test Oracle with extra engine arguments""" mock_settings = self._create_mock_settings() @@ -186,7 +186,7 @@ def test_extra_engine_args(self, mock_get_settings): # Test that default args are still present assert not oracle.engine_args["echo"] - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_autoflush_and_expire_on_commit(self, mock_get_settings): """Test Oracle autoflush and expire_on_commit parameters""" mock_settings = self._create_mock_settings() @@ -197,7 +197,7 @@ def test_autoflush_and_expire_on_commit(self, mock_get_settings): assert not oracle._session_config.autoflush assert not oracle._session_config.expire_on_commit - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_autocommit_parameter(self, mock_get_settings): """Test Oracle autocommit parameter""" mock_settings = self._create_mock_settings() @@ -207,7 +207,7 @@ def test_autocommit_parameter(self, mock_get_settings): assert not oracle._session_config.autocommit - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_connection_timeout_parameter(self, mock_get_settings): """Test Oracle connection_timeout parameter""" mock_settings = self._create_mock_settings() @@ -217,7 +217,7 @@ def test_connection_timeout_parameter(self, mock_get_settings): assert oracle._pool_config.connection_timeout == 60 - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_all_parameters_defaults(self, mock_get_settings): """Test Oracle parameters use settings defaults""" mock_settings = self._create_mock_settings() @@ -230,7 +230,7 @@ def test_all_parameters_defaults(self, mock_get_settings): assert not oracle._session_config.autocommit assert oracle._pool_config.connection_timeout == 30 - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_pool_recycle_parameter(self, mock_get_settings): """Test Oracle pool_recycle parameter""" mock_settings = self._create_mock_settings() @@ -241,7 +241,7 @@ def test_pool_recycle_parameter(self, mock_get_settings): assert oracle._pool_config.pool_recycle == 7200 assert oracle.engine_args["pool_recycle"] == 7200 - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_pool_size_parameter(self, mock_get_settings): """Test Oracle pool_size parameter""" mock_settings = self._create_mock_settings() @@ -252,7 +252,7 @@ def test_pool_size_parameter(self, mock_get_settings): assert oracle._pool_config.pool_size == 15 assert oracle.engine_args["pool_size"] == 15 - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_max_overflow_parameter(self, mock_get_settings): """Test Oracle max_overflow parameter""" mock_settings = self._create_mock_settings() @@ -263,7 +263,7 @@ def test_max_overflow_parameter(self, mock_get_settings): assert oracle._pool_config.max_overflow == 30 assert oracle.engine_args["max_overflow"] == 30 - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_pool_parameters_defaults(self, mock_get_settings): """Test Oracle pool parameters use settings defaults""" mock_settings = self._create_mock_settings() @@ -278,7 +278,7 @@ def test_pool_parameters_defaults(self, mock_get_settings): assert oracle.engine_args["pool_size"] == 10 assert oracle.engine_args["max_overflow"] == 20 - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_ssl_wallet_path(self, mock_get_settings): """Test Oracle SSL wallet path configuration""" mock_settings = self._create_mock_settings() @@ -290,7 +290,7 @@ def test_ssl_wallet_path(self, mock_get_settings): assert oracle._ssl_config.ssl_wallet_path == "/path/to/wallet" assert oracle.engine_args["connect_args"]["wallet_location"] == "/path/to/wallet" - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_ssl_disabled_no_wallet(self, mock_get_settings): """Test Oracle without SSL wallet does not add wallet_location""" mock_settings = self._create_mock_settings() @@ -302,7 +302,7 @@ def test_ssl_disabled_no_wallet(self, mock_get_settings): assert oracle._ssl_config.ssl_wallet_path is None assert "wallet_location" not in oracle.engine_args["connect_args"] - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_get_connection_info(self, mock_get_settings): """Test get_connection_info returns connection config""" mock_get_settings.return_value = self._create_mock_settings() @@ -315,7 +315,7 @@ def test_get_connection_info(self, mock_get_settings): assert conn_info.host == "localhost" assert conn_info.port == 1521 - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_get_pool_info(self, mock_get_settings): """Test get_pool_info returns pool config""" mock_get_settings.return_value = self._create_mock_settings() @@ -328,7 +328,7 @@ def test_get_pool_info(self, mock_get_settings): assert pool_info.pool_size == 20 assert pool_info.max_overflow == 40 - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_get_session_info(self, mock_get_settings): """Test get_session_info returns session config""" mock_get_settings.return_value = self._create_mock_settings() @@ -341,7 +341,7 @@ def test_get_session_info(self, mock_get_settings): assert session_info.echo assert not session_info.autoflush - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_get_connection_retry_info(self, mock_get_settings): """Test get_connection_retry_info returns connection retry config""" mock_get_settings.return_value = self._create_mock_settings() @@ -353,7 +353,7 @@ def test_get_connection_retry_info(self, mock_get_settings): assert hasattr(conn_retry_info, "enable_retry") assert hasattr(conn_retry_info, "max_retries") - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_get_operation_retry_info(self, mock_get_settings): """Test get_operation_retry_info returns operation retry config""" mock_get_settings.return_value = self._create_mock_settings() @@ -366,7 +366,7 @@ def test_get_operation_retry_info(self, mock_get_settings): assert hasattr(op_retry_info, "max_retries") assert hasattr(op_retry_info, "jitter") - @patch("ddcDatabases.oracle.get_oracle_settings") + @patch("ddcdatabases.oracle.get_oracle_settings") def test_get_ssl_info(self, mock_get_settings): """Test get_ssl_info returns SSL config""" mock_get_settings.return_value = self._create_mock_settings() diff --git a/tests/unit/oracle/test_oracle_persistent.py b/tests/unit/oracle/test_oracle_persistent.py index 161882a..88b8ab3 100644 --- a/tests/unit/oracle/test_oracle_persistent.py +++ b/tests/unit/oracle/test_oracle_persistent.py @@ -1,7 +1,7 @@ """Tests for Oracle persistent connections.""" # noinspection PyProtectedMember -from ddcDatabases.core.persistent import ( +from ddcdatabases.core.persistent import ( OraclePersistent, PersistentSQLAlchemyConnection, close_all_persistent_connections, diff --git a/tests/unit/oracle/test_oracle_ssl.py b/tests/unit/oracle/test_oracle_ssl.py index 4baeff7..26d4e10 100644 --- a/tests/unit/oracle/test_oracle_ssl.py +++ b/tests/unit/oracle/test_oracle_ssl.py @@ -8,7 +8,7 @@ class TestOracleSSLConfig: def test_default_values_are_none(self): """Test Oracle SSL config default values are None.""" - from ddcDatabases.oracle import OracleSSLConfig + from ddcdatabases.oracle import OracleSSLConfig config = OracleSSLConfig() assert config.ssl_enabled is None @@ -16,7 +16,7 @@ def test_default_values_are_none(self): def test_ssl_enabled_with_wallet(self): """Test Oracle SSL enabled with wallet path.""" - from ddcDatabases.oracle import OracleSSLConfig + from ddcdatabases.oracle import OracleSSLConfig config = OracleSSLConfig( ssl_enabled=True, @@ -27,7 +27,7 @@ def test_ssl_enabled_with_wallet(self): def test_ssl_config_immutability(self): """Test that SSL config is immutable (frozen).""" - from ddcDatabases.oracle import OracleSSLConfig + from ddcdatabases.oracle import OracleSSLConfig config = OracleSSLConfig(ssl_enabled=True) with pytest.raises(AttributeError): @@ -35,14 +35,14 @@ def test_ssl_config_immutability(self): def test_ssl_disabled(self): """Test Oracle SSL disabled configuration.""" - from ddcDatabases.oracle import OracleSSLConfig + from ddcdatabases.oracle import OracleSSLConfig config = OracleSSLConfig(ssl_enabled=False) assert config.ssl_enabled is False def test_ssl_enabled_without_wallet(self): """Test Oracle SSL enabled without wallet path.""" - from ddcDatabases.oracle import OracleSSLConfig + from ddcdatabases.oracle import OracleSSLConfig config = OracleSSLConfig(ssl_enabled=True) assert config.ssl_enabled is True @@ -50,7 +50,7 @@ def test_ssl_enabled_without_wallet(self): def test_wallet_path_only(self): """Test Oracle SSL config with only wallet path.""" - from ddcDatabases.oracle import OracleSSLConfig + from ddcdatabases.oracle import OracleSSLConfig config = OracleSSLConfig(ssl_wallet_path="/path/to/wallet") assert config.ssl_wallet_path == "/path/to/wallet" diff --git a/tests/unit/postgresql/test_postgresql.py b/tests/unit/postgresql/test_postgresql.py index 287f279..06e9e07 100644 --- a/tests/unit/postgresql/test_postgresql.py +++ b/tests/unit/postgresql/test_postgresql.py @@ -7,7 +7,7 @@ pytestmark = pytest.mark.skipif(not POSTGRESQL_AVAILABLE, reason="PostgreSQL drivers not available") -from ddcDatabases.postgresql import ( +from ddcdatabases.postgresql import ( PostgreSQL, PostgreSQLPoolConfig, PostgreSQLSessionConfig, @@ -62,15 +62,15 @@ class TestPostgreSQL: def setup_method(self): """Clear cache before each test""" # Clear all settings caches before each test - from ddcDatabases.core.settings import get_postgresql_settings + from ddcdatabases.core.settings import get_postgresql_settings # Force clear the cache get_postgresql_settings.cache_clear() # Also clear the module-level dotenv flag - import ddcDatabases.core.settings + import ddcdatabases.core.settings - ddcDatabases.core.settings._dotenv_loaded = False + ddcdatabases.core.settings._dotenv_loaded = False def test_init_missing_credentials(self): """Test PostgreSQL initialization with missing credentials""" @@ -103,7 +103,7 @@ def patched_init(postgresql_self, *_args, **_kwargs): with pytest.raises(RuntimeError, match="Missing username/password"): PostgreSQL() - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_init_with_parameters(self, mock_get_settings): """Test PostgreSQL initialization with override parameters""" mock_settings = _make_pg_mock_settings( @@ -130,7 +130,7 @@ def test_init_with_parameters(self, mock_get_settings): assert postgresql.connection_url["password"] == "custompass" assert postgresql._session_config.echo - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_extra_engine_args(self, mock_get_settings): """Test PostgreSQL with extra engine arguments""" mock_settings = _make_pg_mock_settings() @@ -147,7 +147,7 @@ def test_extra_engine_args(self, mock_get_settings): # Test that default args are still present assert not postgresql.engine_args["echo"] - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_autoflush_and_expire_on_commit(self, mock_get_settings): """Test PostgreSQL autoflush and expire_on_commit parameters""" mock_settings = _make_pg_mock_settings() @@ -158,7 +158,7 @@ def test_autoflush_and_expire_on_commit(self, mock_get_settings): assert not postgresql._session_config.autoflush assert not postgresql._session_config.expire_on_commit - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_autocommit_parameter(self, mock_get_settings): """Test PostgreSQL autocommit parameter""" mock_settings = _make_pg_mock_settings() @@ -168,7 +168,7 @@ def test_autocommit_parameter(self, mock_get_settings): assert postgresql._session_config.autocommit - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_connect_args_psycopg_driver(self, mock_get_settings): """Test that psycopg driver sets correct connect_args""" mock_settings = _make_pg_mock_settings() @@ -200,7 +200,7 @@ def test_connect_args_different_driver(self): assert "psycopg" not in test_driver_pg8000 assert "pg8000" in test_driver_pg8000 - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_driver_detection_logic(self, mock_get_settings): """Test driver detection logic in init""" mock_settings = _make_pg_mock_settings() @@ -248,7 +248,7 @@ def test_engine_args_structure(self): assert hasattr(postgresql, "extra_engine_args") assert postgresql.extra_engine_args == extra_args - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_context_manager_methods_exist(self, mock_get_settings): """Test that context manager methods exist and can be called safely""" mock_settings = _make_pg_mock_settings() @@ -262,7 +262,7 @@ def test_context_manager_methods_exist(self, mock_get_settings): assert callable(postgresql._get_engine) assert callable(postgresql._get_async_engine) - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_get_base_engine_args_method(self, mock_get_settings): """Test _get_base_engine_args method - covers lines 70-87""" from sqlalchemy import URL @@ -306,7 +306,7 @@ def test_get_base_engine_args_method(self, mock_get_settings): assert result["connect_args"]["connect_timeout"] == 30 assert result["isolation_level"] == "READ_COMMITTED" - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_sync_driver_autocommit_logic(self, mock_get_settings): """Test autocommit logic for sync driver - covers autocommit branch""" mock_settings = _make_pg_mock_settings() @@ -336,7 +336,7 @@ def test_sync_driver_autocommit_logic(self, mock_get_settings): assert "isolation_level" in engine_args assert engine_args["isolation_level"] == "AUTOCOMMIT" - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_async_driver_autocommit_logic(self, mock_get_settings): """Test autocommit logic for async driver - covers async autocommit branch""" mock_settings = _make_pg_mock_settings() @@ -369,7 +369,7 @@ def test_async_driver_autocommit_logic(self, mock_get_settings): assert "command_timeout" in engine_args["connect_args"] assert engine_args["connect_args"]["command_timeout"] == 30 - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_pool_size_parameter(self, mock_get_settings): """Test PostgreSQL pool_size parameter""" mock_settings = _make_pg_mock_settings(pool_size=10) @@ -379,7 +379,7 @@ def test_pool_size_parameter(self, mock_get_settings): assert postgresql._pool_config.pool_size == 15 - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_max_overflow_parameter(self, mock_get_settings): """Test PostgreSQL max_overflow parameter""" mock_settings = _make_pg_mock_settings(max_overflow=20) @@ -389,7 +389,7 @@ def test_max_overflow_parameter(self, mock_get_settings): assert postgresql._pool_config.max_overflow == 30 - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_pool_parameters_defaults(self, mock_get_settings): """Test PostgreSQL pool parameters use settings defaults""" mock_settings = _make_pg_mock_settings() @@ -400,7 +400,7 @@ def test_pool_parameters_defaults(self, mock_get_settings): assert postgresql._pool_config.pool_size == 25 assert postgresql._pool_config.max_overflow == 50 - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_enhanced_configuration_methods(self, mock_get_settings): """Test the new enhanced configuration getter methods""" mock_settings = _make_pg_mock_settings( @@ -463,7 +463,7 @@ def test_enhanced_configuration_methods(self, mock_get_settings): assert not session_config.expire_on_commit assert session_config.autocommit - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_get_engine_method_with_psycopg(self, mock_get_settings): """Test the _get_engine method with psycopg driver""" mock_settings = _make_pg_mock_settings( @@ -492,7 +492,7 @@ def test_get_engine_method_with_psycopg(self, mock_get_settings): # Verify autocommit is configured via isolation_level in the URL or engine assert "psycopg" in str(engine.url) - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_get_engine_method_without_autocommit(self, mock_get_settings): """Test the _get_engine method without autocommit""" mock_settings = _make_pg_mock_settings( @@ -520,7 +520,7 @@ def test_get_engine_method_without_autocommit(self, mock_get_settings): assert hasattr(engine, "url") assert "psycopg" in str(engine.url) - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_get_engine_method_non_psycopg_driver(self, mock_get_settings): """Test the _get_engine method with non-psycopg driver""" mock_settings = _make_pg_mock_settings( @@ -548,7 +548,7 @@ def test_get_engine_method_non_psycopg_driver(self, mock_get_settings): assert hasattr(engine, "url") assert "psycopg" in str(engine.url) - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_get_async_engine_method_with_asyncpg(self, mock_get_settings): """Test the _get_async_engine method with asyncpg driver""" mock_settings = _make_pg_mock_settings(connection_timeout=45) @@ -574,7 +574,7 @@ async def test_async(): asyncio.run(test_async()) - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_get_async_engine_method_without_autocommit(self, mock_get_settings): """Test the _get_async_engine method without autocommit""" mock_settings = _make_pg_mock_settings() @@ -595,7 +595,7 @@ async def test_async(): asyncio.run(test_async()) - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_get_async_engine_method_non_asyncpg_driver(self, mock_get_settings): """Test the _get_async_engine method with non-asyncpg driver""" mock_settings = _make_pg_mock_settings() @@ -616,7 +616,7 @@ async def test_async(): asyncio.run(test_async()) - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_repr_method(self, mock_get_settings): """Test the enhanced __repr__ method""" mock_settings = _make_pg_mock_settings( @@ -646,7 +646,7 @@ def test_repr_method(self, mock_get_settings): assert "echo=True" in repr_str assert ")" in repr_str - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_configuration_immutability(self, mock_get_settings): """Test that configuration objects are properly immutable""" mock_settings = _make_pg_mock_settings() @@ -669,7 +669,7 @@ def test_configuration_immutability(self, mock_get_settings): with pytest.raises(dataclasses.FrozenInstanceError): session_config.echo = True # noqa - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_schema_default(self, mock_get_settings): """Test PostgreSQL schema defaults to public""" mock_settings = _make_pg_mock_settings() @@ -681,7 +681,7 @@ def test_schema_default(self, mock_get_settings): conn_config = postgresql.get_connection_info() assert conn_config.schema == "public" - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_schema_custom(self, mock_get_settings): """Test PostgreSQL with custom schema sets search_path""" mock_settings = _make_pg_mock_settings() @@ -706,7 +706,7 @@ def test_schema_custom(self, mock_get_settings): assert sync_connect_args["options"] == "-c search_path=custom_schema" - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_schema_public_no_options(self, mock_get_settings): """Test PostgreSQL with public schema does not set search_path options""" mock_settings = _make_pg_mock_settings() @@ -725,7 +725,7 @@ def test_schema_public_no_options(self, mock_get_settings): assert "options" not in sync_connect_args - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_schema_comma_separated(self, mock_get_settings): """Test PostgreSQL with comma-separated schemas sets correct search_path.""" mock_settings = _make_pg_mock_settings() @@ -752,7 +752,7 @@ def test_schema_comma_separated(self, mock_get_settings): assert async_connect_args["server_settings"] == {"search_path": "gw2,public"} - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_ssl_enabled_with_options(self, mock_get_settings): """Test PostgreSQL SSL configuration""" mock_settings = _make_pg_mock_settings() @@ -789,7 +789,7 @@ def test_ssl_enabled_with_options(self, mock_get_settings): assert sync_connect_args["sslcert"] == "/path/to/client.pem" assert sync_connect_args["sslkey"] == "/path/to/client-key.pem" - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_ssl_disabled_no_connect_args(self, mock_get_settings): """Test PostgreSQL with SSL disabled does not add SSL connect_args""" mock_settings = _make_pg_mock_settings() @@ -806,10 +806,10 @@ def test_ssl_disabled_no_connect_args(self, mock_get_settings): assert "sslmode" not in sync_connect_args - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_get_connection_info(self, mock_get_settings): """Test get_connection_info returns connection config""" - from ddcDatabases.postgresql import PostgreSQL, PostgreSQLConnectionConfig + from ddcdatabases.postgresql import PostgreSQL, PostgreSQLConnectionConfig mock_settings = _make_pg_mock_settings( operation_initial_retry_delay=1.0, @@ -826,10 +826,10 @@ def test_get_connection_info(self, mock_get_settings): assert conn_info.host == "localhost" assert conn_info.port == 5432 - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_get_pool_info(self, mock_get_settings): """Test get_pool_info returns pool config""" - from ddcDatabases.postgresql import PostgreSQL, PostgreSQLPoolConfig + from ddcdatabases.postgresql import PostgreSQL, PostgreSQLPoolConfig mock_settings = _make_pg_mock_settings( operation_initial_retry_delay=1.0, @@ -846,10 +846,10 @@ def test_get_pool_info(self, mock_get_settings): assert pool_info.pool_size == 20 assert pool_info.max_overflow == 40 - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_get_session_info(self, mock_get_settings): """Test get_session_info returns session config""" - from ddcDatabases.postgresql import PostgreSQL, PostgreSQLSessionConfig + from ddcdatabases.postgresql import PostgreSQL, PostgreSQLSessionConfig mock_settings = _make_pg_mock_settings( operation_initial_retry_delay=1.0, @@ -866,10 +866,10 @@ def test_get_session_info(self, mock_get_settings): assert session_info.echo assert not session_info.autoflush - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_get_operation_retry_info(self, mock_get_settings): """Test get_operation_retry_info returns operation retry config""" - from ddcDatabases.postgresql import PostgreSQL + from ddcdatabases.postgresql import PostgreSQL mock_settings = _make_pg_mock_settings( operation_initial_retry_delay=1.0, diff --git a/tests/unit/postgresql/test_postgresql_persistent.py b/tests/unit/postgresql/test_postgresql_persistent.py index fb062cf..5cc0a01 100644 --- a/tests/unit/postgresql/test_postgresql_persistent.py +++ b/tests/unit/postgresql/test_postgresql_persistent.py @@ -1,10 +1,10 @@ """Tests for PostgreSQL persistent connections.""" import pytest -from ddcDatabases.core.configs import BaseRetryConfig as RetryConfig +from ddcdatabases.core.configs import BaseRetryConfig as RetryConfig # noinspection PyProtectedMember -from ddcDatabases.core.persistent import ( +from ddcdatabases.core.persistent import ( PersistentConnectionConfig, PersistentSQLAlchemyAsyncConnection, PersistentSQLAlchemyConnection, diff --git a/tests/unit/postgresql/test_postgresql_ssl.py b/tests/unit/postgresql/test_postgresql_ssl.py index 46834db..8e04eda 100644 --- a/tests/unit/postgresql/test_postgresql_ssl.py +++ b/tests/unit/postgresql/test_postgresql_ssl.py @@ -16,8 +16,8 @@ class TestPostgreSQLSSLConfig: def test_valid_ssl_modes(self): """Test all valid PostgreSQL SSL modes.""" - from ddcDatabases.core.constants import POSTGRESQL_SSL_MODES - from ddcDatabases.postgresql import PostgreSQLSSLConfig + from ddcdatabases.core.constants import POSTGRESQL_SSL_MODES + from ddcdatabases.postgresql import PostgreSQLSSLConfig for mode in POSTGRESQL_SSL_MODES: config = PostgreSQLSSLConfig(ssl_mode=mode) @@ -25,14 +25,14 @@ def test_valid_ssl_modes(self): def test_invalid_ssl_mode_raises_error(self): """Test that invalid SSL mode raises ValueError.""" - from ddcDatabases.postgresql import PostgreSQLSSLConfig + from ddcdatabases.postgresql import PostgreSQLSSLConfig with pytest.raises(ValueError, match="ssl_mode must be one of"): PostgreSQLSSLConfig(ssl_mode="invalid_mode") def test_ssl_config_with_all_paths(self): """Test SSL config with all certificate paths.""" - from ddcDatabases.postgresql import PostgreSQLSSLConfig + from ddcdatabases.postgresql import PostgreSQLSSLConfig config = PostgreSQLSSLConfig( ssl_mode="verify-full", @@ -47,7 +47,7 @@ def test_ssl_config_with_all_paths(self): def test_ssl_config_immutability(self): """Test that SSL config is immutable (frozen).""" - from ddcDatabases.postgresql import PostgreSQLSSLConfig + from ddcdatabases.postgresql import PostgreSQLSSLConfig config = PostgreSQLSSLConfig(ssl_mode="require") with pytest.raises(AttributeError): @@ -55,21 +55,21 @@ def test_ssl_config_immutability(self): def test_ssl_mode_none_by_default(self): """Test default SSL mode is None (not set).""" - from ddcDatabases.postgresql import PostgreSQLSSLConfig + from ddcdatabases.postgresql import PostgreSQLSSLConfig config = PostgreSQLSSLConfig() assert config.ssl_mode is None def test_ssl_modes_case_insensitive_validation(self): """Test that PostgreSQL SSL mode validation is case-insensitive.""" - from ddcDatabases.postgresql import PostgreSQLSSLConfig + from ddcdatabases.postgresql import PostgreSQLSSLConfig config = PostgreSQLSSLConfig(ssl_mode="REQUIRE") assert config.ssl_mode == "REQUIRE" def test_verify_ca_mode(self): """Test verify-ca SSL mode.""" - from ddcDatabases.postgresql import PostgreSQLSSLConfig + from ddcdatabases.postgresql import PostgreSQLSSLConfig config = PostgreSQLSSLConfig( ssl_mode="verify-ca", @@ -79,7 +79,7 @@ def test_verify_ca_mode(self): def test_all_ssl_modes_are_valid(self): """Test that all documented SSL modes are accepted.""" - from ddcDatabases.postgresql import PostgreSQLSSLConfig + from ddcdatabases.postgresql import PostgreSQLSSLConfig valid_modes = ["disable", "allow", "prefer", "require", "verify-ca", "verify-full"] for mode in valid_modes: @@ -88,7 +88,7 @@ def test_all_ssl_modes_are_valid(self): def test_ssl_ca_cert_path_only(self): """Test SSL config with only CA certificate path.""" - from ddcDatabases.postgresql import PostgreSQLSSLConfig + from ddcdatabases.postgresql import PostgreSQLSSLConfig config = PostgreSQLSSLConfig( ssl_mode="verify-ca", @@ -100,7 +100,7 @@ def test_ssl_ca_cert_path_only(self): def test_ssl_client_cert_without_key(self): """Test SSL config with client cert but no key.""" - from ddcDatabases.postgresql import PostgreSQLSSLConfig + from ddcdatabases.postgresql import PostgreSQLSSLConfig config = PostgreSQLSSLConfig( ssl_mode="verify-full", @@ -116,18 +116,18 @@ class TestPostgreSQLSSLEngine: # noinspection PyMethodMayBeStatic def setup_method(self): """Clear cache before each test.""" - from ddcDatabases.core.settings import get_postgresql_settings + from ddcdatabases.core.settings import get_postgresql_settings get_postgresql_settings.cache_clear() - import ddcDatabases.core.settings + import ddcdatabases.core.settings - ddcDatabases.core.settings._dotenv_loaded = False + ddcdatabases.core.settings._dotenv_loaded = False - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_async_engine_ssl_with_cert_paths_uses_ssl_context(self, mock_get_settings): """Test that _get_async_engine passes an ssl.SSLContext when cert paths are provided.""" - from ddcDatabases.postgresql import PostgreSQL, PostgreSQLSSLConfig + from ddcdatabases.postgresql import PostgreSQL, PostgreSQLSSLConfig mock_settings = MagicMock() mock_settings.host = "localhost" @@ -174,8 +174,8 @@ def test_async_engine_ssl_with_cert_paths_uses_ssl_context(self, mock_get_settin mock_ssl_context = MagicMock(spec=ssl.SSLContext) with ( - patch("ddcDatabases.postgresql.create_async_engine") as mock_create, - patch("ddcDatabases.postgresql._ssl_module.SSLContext", return_value=mock_ssl_context), + patch("ddcdatabases.postgresql.create_async_engine") as mock_create, + patch("ddcdatabases.postgresql._ssl_module.SSLContext", return_value=mock_ssl_context), ): mock_engine = MagicMock() mock_engine.dispose = AsyncMock() @@ -199,10 +199,10 @@ async def _test(): keyfile="/path/to/client-key.pem", ) - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_async_engine_ssl_without_cert_paths_uses_mode_string(self, mock_get_settings): """Test that _get_async_engine passes the mode string when no cert paths are provided.""" - from ddcDatabases.postgresql import PostgreSQL, PostgreSQLSSLConfig + from ddcdatabases.postgresql import PostgreSQL, PostgreSQLSSLConfig mock_settings = MagicMock() mock_settings.host = "localhost" @@ -240,7 +240,7 @@ def test_async_engine_ssl_without_cert_paths_uses_mode_string(self, mock_get_set captured_args = {} - with patch("ddcDatabases.postgresql.create_async_engine") as mock_create: + with patch("ddcdatabases.postgresql.create_async_engine") as mock_create: mock_engine = MagicMock() mock_engine.dispose = AsyncMock() mock_create.return_value = mock_engine @@ -256,10 +256,10 @@ async def _test(): connect_args = captured_args.get("connect_args", {}) assert connect_args.get("ssl") == "require", f"Expected mode string 'require', got {connect_args.get('ssl')!r}" - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_async_engine_ssl_ca_only_no_client_certs(self, mock_get_settings): """Test that _get_async_engine creates SSLContext with CA cert only (no client cert/key).""" - from ddcDatabases.postgresql import PostgreSQL, PostgreSQLSSLConfig + from ddcdatabases.postgresql import PostgreSQL, PostgreSQLSSLConfig mock_settings = MagicMock() mock_settings.host = "localhost" @@ -304,8 +304,8 @@ def test_async_engine_ssl_ca_only_no_client_certs(self, mock_get_settings): mock_ssl_context = MagicMock(spec=ssl.SSLContext) with ( - patch("ddcDatabases.postgresql.create_async_engine") as mock_create, - patch("ddcDatabases.postgresql._ssl_module.SSLContext", return_value=mock_ssl_context), + patch("ddcdatabases.postgresql.create_async_engine") as mock_create, + patch("ddcdatabases.postgresql._ssl_module.SSLContext", return_value=mock_ssl_context), ): mock_engine = MagicMock() mock_engine.dispose = AsyncMock() @@ -325,10 +325,10 @@ async def _test(): mock_ssl_context.load_verify_locations.assert_called_once_with(cafile="/path/to/ca.pem") mock_ssl_context.load_cert_chain.assert_not_called() - @patch("ddcDatabases.postgresql.get_postgresql_settings") + @patch("ddcdatabases.postgresql.get_postgresql_settings") def test_sync_engine_ssl_with_cert_paths(self, mock_get_settings): """Test that _get_engine passes SSL cert paths as connect_args for psycopg.""" - from ddcDatabases.postgresql import PostgreSQL, PostgreSQLSSLConfig + from ddcdatabases.postgresql import PostgreSQL, PostgreSQLSSLConfig mock_settings = MagicMock() mock_settings.host = "localhost" @@ -371,7 +371,7 @@ def test_sync_engine_ssl_with_cert_paths(self, mock_get_settings): ) ) - with patch("ddcDatabases.postgresql.create_engine") as mock_create: + with patch("ddcdatabases.postgresql.create_engine") as mock_create: mock_engine = MagicMock() mock_create.return_value = mock_engine @@ -391,17 +391,17 @@ class TestPostgreSQLSSLEnvVars: # noinspection PyMethodMayBeStatic def setup_method(self): """Clear cache before each test.""" - from ddcDatabases.core.settings import get_postgresql_settings + from ddcdatabases.core.settings import get_postgresql_settings get_postgresql_settings.cache_clear() - import ddcDatabases.core.settings + import ddcdatabases.core.settings - ddcDatabases.core.settings._dotenv_loaded = False + ddcdatabases.core.settings._dotenv_loaded = False def test_ssl_cert_paths_from_env_vars(self): """Test that SSL cert paths are read from POSTGRESQL_SSL_* env vars.""" - from ddcDatabases.core.settings import PostgreSQLSettings + from ddcdatabases.core.settings import PostgreSQLSettings with patch.dict( os.environ, @@ -420,8 +420,8 @@ def test_ssl_cert_paths_from_env_vars(self): def test_ssl_env_vars_propagate_to_async_engine(self): """Test that SSL cert paths from env vars result in SSLContext for async engine.""" - from ddcDatabases.core.settings import PostgreSQLSettings - from ddcDatabases.postgresql import PostgreSQL + from ddcdatabases.core.settings import PostgreSQLSettings + from ddcdatabases.postgresql import PostgreSQL with patch.dict( os.environ, @@ -434,7 +434,7 @@ def test_ssl_env_vars_propagate_to_async_engine(self): ): mock_settings = PostgreSQLSettings() - with patch("ddcDatabases.postgresql.get_postgresql_settings", return_value=mock_settings): + with patch("ddcdatabases.postgresql.get_postgresql_settings", return_value=mock_settings): postgresql = PostgreSQL() assert postgresql._ssl_config.ssl_ca_cert_path == "/env/path/to/ca.pem" @@ -445,8 +445,8 @@ def test_ssl_env_vars_propagate_to_async_engine(self): mock_ssl_context = MagicMock(spec=ssl.SSLContext) with ( - patch("ddcDatabases.postgresql.create_async_engine") as mock_create, - patch("ddcDatabases.postgresql._ssl_module.SSLContext", return_value=mock_ssl_context), + patch("ddcdatabases.postgresql.create_async_engine") as mock_create, + patch("ddcdatabases.postgresql._ssl_module.SSLContext", return_value=mock_ssl_context), ): mock_engine = MagicMock() mock_engine.dispose = AsyncMock() diff --git a/tests/unit/sqlite/test_sqlite.py b/tests/unit/sqlite/test_sqlite.py index dc02bb2..2c3b723 100644 --- a/tests/unit/sqlite/test_sqlite.py +++ b/tests/unit/sqlite/test_sqlite.py @@ -1,7 +1,7 @@ import pytest import sqlalchemy as sa import tempfile -from ddcDatabases.sqlite import SqliteSessionConfig +from ddcdatabases.sqlite import SqliteSessionConfig from sqlalchemy import Boolean from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column from unittest.mock import MagicMock, patch @@ -25,12 +25,12 @@ class TestSQLite: def setup_method(self): """Import dependencies when needed""" - from ddcDatabases import DBUtils, Sqlite + from ddcdatabases import DBUtils, Sqlite self.Sqlite = Sqlite self.DBUtils = DBUtils - @patch("ddcDatabases.sqlite.get_sqlite_settings") + @patch("ddcdatabases.sqlite.get_sqlite_settings") def test_init_basic(self, mock_get_settings): """Test SQLite basic initialization""" mock_settings = MagicMock() @@ -53,7 +53,7 @@ def test_init_basic(self, mock_get_settings): assert not sqlite.echo assert not sqlite.is_connected - @patch("ddcDatabases.sqlite.get_sqlite_settings") + @patch("ddcdatabases.sqlite.get_sqlite_settings") def test_init_with_parameters(self, mock_get_settings): """Test SQLite initialization with parameters""" mock_settings = MagicMock() @@ -169,7 +169,7 @@ def test_context_manager(self): sqlite.__exit__(None, None, None) assert not sqlite.is_connected - @patch("ddcDatabases.sqlite.create_engine") + @patch("ddcdatabases.sqlite.create_engine") def test_engine_creation_error(self, mock_create_engine): """Test SQLite engine creation error handling""" mock_create_engine.side_effect = Exception("Engine creation failed") @@ -201,7 +201,7 @@ class TestSQLiteInfoMethods: """Test SQLite info getter methods""" def setup_method(self): - from ddcDatabases import Sqlite + from ddcdatabases import Sqlite self.Sqlite = Sqlite @@ -229,7 +229,7 @@ class TestSQLiteRealOperations: def setup_method(self): """Import dependencies when needed""" - from ddcDatabases import DBUtils, Sqlite + from ddcdatabases import DBUtils, Sqlite self.Sqlite = Sqlite self.DBUtils = DBUtils diff --git a/uv.lock b/uv.lock index a69fb4a..7d5791a 100644 --- a/uv.lock +++ b/uv.lock @@ -205,91 +205,107 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.4" +version = "3.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, - { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, - { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, - { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, - { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, - { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, - { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, - { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, - { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, - { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, - { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, - { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, - { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, - { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, - { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, - { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, - { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, - { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, - { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, - { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, - { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, - { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, - { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, - { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, - { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, - { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, - { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, - { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, - { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, - { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, - { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, - { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, - { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, - { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, - { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, - { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, - { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, - { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, - { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, - { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, - { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, - { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, - { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, - { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, - { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, - { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, - { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, - { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, - { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, - { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, - { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, - { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, - { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, - { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, - { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, - { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, - { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, - { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/2c56124c6dc53a774d435f985b5973bc592f42d437be58c0c92d65ae7296/charset_normalizer-3.4.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95", size = 298751, upload-time = "2026-03-15T18:50:00.003Z" }, + { url = "https://files.pythonhosted.org/packages/86/2a/2a7db6b314b966a3bcad8c731c0719c60b931b931de7ae9f34b2839289ee/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd", size = 200027, upload-time = "2026-03-15T18:50:01.702Z" }, + { url = "https://files.pythonhosted.org/packages/68/f2/0fe775c74ae25e2a3b07b01538fc162737b3e3f795bada3bc26f4d4d495c/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:259695e2ccc253feb2a016303543d691825e920917e31f894ca1a687982b1de4", size = 220741, upload-time = "2026-03-15T18:50:03.194Z" }, + { url = "https://files.pythonhosted.org/packages/10/98/8085596e41f00b27dd6aa1e68413d1ddda7e605f34dd546833c61fddd709/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dda86aba335c902b6149a02a55b38e96287157e609200811837678214ba2b1db", size = 215802, upload-time = "2026-03-15T18:50:05.859Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ce/865e4e09b041bad659d682bbd98b47fb490b8e124f9398c9448065f64fee/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51fb3c322c81d20567019778cb5a4a6f2dc1c200b886bc0d636238e364848c89", size = 207908, upload-time = "2026-03-15T18:50:07.676Z" }, + { url = "https://files.pythonhosted.org/packages/a8/54/8c757f1f7349262898c2f169e0d562b39dcb977503f18fdf0814e923db78/charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:4482481cb0572180b6fd976a4d5c72a30263e98564da68b86ec91f0fe35e8565", size = 194357, upload-time = "2026-03-15T18:50:09.327Z" }, + { url = "https://files.pythonhosted.org/packages/6f/29/e88f2fac9218907fc7a70722b393d1bbe8334c61fe9c46640dba349b6e66/charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39f5068d35621da2881271e5c3205125cc456f54e9030d3f723288c873a71bf9", size = 205610, upload-time = "2026-03-15T18:50:10.732Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c5/21d7bb0cb415287178450171d130bed9d664211fdd59731ed2c34267b07d/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8bea55c4eef25b0b19a0337dc4e3f9a15b00d569c77211fa8cde38684f234fb7", size = 203512, upload-time = "2026-03-15T18:50:12.535Z" }, + { url = "https://files.pythonhosted.org/packages/a4/be/ce52f3c7fdb35cc987ad38a53ebcef52eec498f4fb6c66ecfe62cfe57ba2/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f0cdaecd4c953bfae0b6bb64910aaaca5a424ad9c72d85cb88417bb9814f7550", size = 195398, upload-time = "2026-03-15T18:50:14.236Z" }, + { url = "https://files.pythonhosted.org/packages/81/a0/3ab5dd39d4859a3555e5dadfc8a9fa7f8352f8c183d1a65c90264517da0e/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:150b8ce8e830eb7ccb029ec9ca36022f756986aaaa7956aad6d9ec90089338c0", size = 221772, upload-time = "2026-03-15T18:50:15.581Z" }, + { url = "https://files.pythonhosted.org/packages/04/6e/6a4e41a97ba6b2fa87f849c41e4d229449a586be85053c4d90135fe82d26/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e68c14b04827dd76dcbd1aeea9e604e3e4b78322d8faf2f8132c7138efa340a8", size = 205759, upload-time = "2026-03-15T18:50:17.047Z" }, + { url = "https://files.pythonhosted.org/packages/db/3b/34a712a5ee64a6957bf355b01dc17b12de457638d436fdb05d01e463cd1c/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3778fd7d7cd04ae8f54651f4a7a0bd6e39a0cf20f801720a4c21d80e9b7ad6b0", size = 216938, upload-time = "2026-03-15T18:50:18.44Z" }, + { url = "https://files.pythonhosted.org/packages/cb/05/5bd1e12da9ab18790af05c61aafd01a60f489778179b621ac2a305243c62/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dad6e0f2e481fffdcf776d10ebee25e0ef89f16d691f1e5dee4b586375fdc64b", size = 210138, upload-time = "2026-03-15T18:50:19.852Z" }, + { url = "https://files.pythonhosted.org/packages/bd/8e/3cb9e2d998ff6b21c0a1860343cb7b83eba9cdb66b91410e18fc4969d6ab/charset_normalizer-3.4.6-cp310-cp310-win32.whl", hash = "sha256:74a2e659c7ecbc73562e2a15e05039f1e22c75b7c7618b4b574a3ea9118d1557", size = 144137, upload-time = "2026-03-15T18:50:21.505Z" }, + { url = "https://files.pythonhosted.org/packages/d8/8f/78f5489ffadb0db3eb7aff53d31c24531d33eb545f0c6f6567c25f49a5ff/charset_normalizer-3.4.6-cp310-cp310-win_amd64.whl", hash = "sha256:aa9cccf4a44b9b62d8ba8b4dd06c649ba683e4bf04eea606d2e94cfc2d6ff4d6", size = 154244, upload-time = "2026-03-15T18:50:22.81Z" }, + { url = "https://files.pythonhosted.org/packages/e4/74/e472659dffb0cadb2f411282d2d76c60da1fc94076d7fffed4ae8a93ec01/charset_normalizer-3.4.6-cp310-cp310-win_arm64.whl", hash = "sha256:e985a16ff513596f217cee86c21371b8cd011c0f6f056d0920aa2d926c544058", size = 143312, upload-time = "2026-03-15T18:50:24.074Z" }, + { url = "https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e", size = 293582, upload-time = "2026-03-15T18:50:25.454Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b7/b1a117e5385cbdb3205f6055403c2a2a220c5ea80b8716c324eaf75c5c95/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9", size = 197240, upload-time = "2026-03-15T18:50:27.196Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5f/2574f0f09f3c3bc1b2f992e20bce6546cb1f17e111c5be07308dc5427956/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d", size = 217363, upload-time = "2026-03-15T18:50:28.601Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d1/0ae20ad77bc949ddd39b51bf383b6ca932f2916074c95cad34ae465ab71f/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de", size = 212994, upload-time = "2026-03-15T18:50:30.102Z" }, + { url = "https://files.pythonhosted.org/packages/60/ac/3233d262a310c1b12633536a07cde5ddd16985e6e7e238e9f3f9423d8eb9/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73", size = 204697, upload-time = "2026-03-15T18:50:31.654Z" }, + { url = "https://files.pythonhosted.org/packages/25/3c/8a18fc411f085b82303cfb7154eed5bd49c77035eb7608d049468b53f87c/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c", size = 191673, upload-time = "2026-03-15T18:50:33.433Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a7/11cfe61d6c5c5c7438d6ba40919d0306ed83c9ab957f3d4da2277ff67836/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc", size = 201120, upload-time = "2026-03-15T18:50:35.105Z" }, + { url = "https://files.pythonhosted.org/packages/b5/10/cf491fa1abd47c02f69687046b896c950b92b6cd7337a27e6548adbec8e4/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f", size = 200911, upload-time = "2026-03-15T18:50:36.819Z" }, + { url = "https://files.pythonhosted.org/packages/28/70/039796160b48b18ed466fde0af84c1b090c4e288fae26cd674ad04a2d703/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef", size = 192516, upload-time = "2026-03-15T18:50:38.228Z" }, + { url = "https://files.pythonhosted.org/packages/ff/34/c56f3223393d6ff3124b9e78f7de738047c2d6bc40a4f16ac0c9d7a1cb3c/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398", size = 218795, upload-time = "2026-03-15T18:50:39.664Z" }, + { url = "https://files.pythonhosted.org/packages/e8/3b/ce2d4f86c5282191a041fdc5a4ce18f1c6bd40a5bd1f74cf8625f08d51c1/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e", size = 201833, upload-time = "2026-03-15T18:50:41.552Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9b/b6a9f76b0fd7c5b5ec58b228ff7e85095370282150f0bd50b3126f5506d6/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed", size = 213920, upload-time = "2026-03-15T18:50:43.33Z" }, + { url = "https://files.pythonhosted.org/packages/ae/98/7bc23513a33d8172365ed30ee3a3b3fe1ece14a395e5fc94129541fc6003/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021", size = 206951, upload-time = "2026-03-15T18:50:44.789Z" }, + { url = "https://files.pythonhosted.org/packages/32/73/c0b86f3d1458468e11aec870e6b3feac931facbe105a894b552b0e518e79/charset_normalizer-3.4.6-cp311-cp311-win32.whl", hash = "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e", size = 143703, upload-time = "2026-03-15T18:50:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e3/76f2facfe8eddee0bbd38d2594e709033338eae44ebf1738bcefe0a06185/charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4", size = 153857, upload-time = "2026-03-15T18:50:47.563Z" }, + { url = "https://files.pythonhosted.org/packages/e2/dc/9abe19c9b27e6cd3636036b9d1b387b78c40dedbf0b47f9366737684b4b0/charset_normalizer-3.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316", size = 142751, upload-time = "2026-03-15T18:50:49.234Z" }, + { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" }, + { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" }, + { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" }, + { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" }, + { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" }, + { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" }, + { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" }, + { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" }, + { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229, upload-time = "2026-03-15T18:51:11.694Z" }, + { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277, upload-time = "2026-03-15T18:51:13.004Z" }, + { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817, upload-time = "2026-03-15T18:51:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823, upload-time = "2026-03-15T18:51:15.755Z" }, + { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527, upload-time = "2026-03-15T18:51:17.177Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388, upload-time = "2026-03-15T18:51:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563, upload-time = "2026-03-15T18:51:20.374Z" }, + { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587, upload-time = "2026-03-15T18:51:21.807Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724, upload-time = "2026-03-15T18:51:23.508Z" }, + { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956, upload-time = "2026-03-15T18:51:25.239Z" }, + { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923, upload-time = "2026-03-15T18:51:26.682Z" }, + { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366, upload-time = "2026-03-15T18:51:28.129Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752, upload-time = "2026-03-15T18:51:29.556Z" }, + { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296, upload-time = "2026-03-15T18:51:30.921Z" }, + { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956, upload-time = "2026-03-15T18:51:32.399Z" }, + { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652, upload-time = "2026-03-15T18:51:34.214Z" }, + { url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940, upload-time = "2026-03-15T18:51:36.15Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101, upload-time = "2026-03-15T18:51:37.876Z" }, + { url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109, upload-time = "2026-03-15T18:51:39.565Z" }, + { url = "https://files.pythonhosted.org/packages/25/6f/ffe1e1259f384594063ea1869bfb6be5cdb8bc81020fc36c3636bc8302a1/charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", size = 294458, upload-time = "2026-03-15T18:51:41.134Z" }, + { url = "https://files.pythonhosted.org/packages/56/60/09bb6c13a8c1016c2ed5c6a6488e4ffef506461aa5161662bd7636936fb1/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", size = 199277, upload-time = "2026-03-15T18:51:42.953Z" }, + { url = "https://files.pythonhosted.org/packages/00/50/dcfbb72a5138bbefdc3332e8d81a23494bf67998b4b100703fd15fa52d81/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", size = 218758, upload-time = "2026-03-15T18:51:44.339Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/d79a9a191bb75f5aa81f3aaaa387ef29ce7cb7a9e5074ba8ea095cc073c2/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", size = 215299, upload-time = "2026-03-15T18:51:45.871Z" }, + { url = "https://files.pythonhosted.org/packages/76/7e/bc8911719f7084f72fd545f647601ea3532363927f807d296a8c88a62c0d/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", size = 206811, upload-time = "2026-03-15T18:51:47.308Z" }, + { url = "https://files.pythonhosted.org/packages/e2/40/c430b969d41dda0c465aa36cc7c2c068afb67177bef50905ac371b28ccc7/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", size = 193706, upload-time = "2026-03-15T18:51:48.849Z" }, + { url = "https://files.pythonhosted.org/packages/48/15/e35e0590af254f7df984de1323640ef375df5761f615b6225ba8deb9799a/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", size = 202706, upload-time = "2026-03-15T18:51:50.257Z" }, + { url = "https://files.pythonhosted.org/packages/5e/bd/f736f7b9cc5e93a18b794a50346bb16fbfd6b37f99e8f306f7951d27c17c/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", size = 202497, upload-time = "2026-03-15T18:51:52.012Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ba/2cc9e3e7dfdf7760a6ed8da7446d22536f3d0ce114ac63dee2a5a3599e62/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", size = 193511, upload-time = "2026-03-15T18:51:53.723Z" }, + { url = "https://files.pythonhosted.org/packages/9e/cb/5be49b5f776e5613be07298c80e1b02a2d900f7a7de807230595c85a8b2e/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", size = 220133, upload-time = "2026-03-15T18:51:55.333Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/99f1b5dad345accb322c80c7821071554f791a95ee50c1c90041c157ae99/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", size = 203035, upload-time = "2026-03-15T18:51:56.736Z" }, + { url = "https://files.pythonhosted.org/packages/87/9a/62c2cb6a531483b55dddff1a68b3d891a8b498f3ca555fbcf2978e804d9d/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", size = 216321, upload-time = "2026-03-15T18:51:58.17Z" }, + { url = "https://files.pythonhosted.org/packages/6e/79/94a010ff81e3aec7c293eb82c28f930918e517bc144c9906a060844462eb/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", size = 208973, upload-time = "2026-03-15T18:51:59.998Z" }, + { url = "https://files.pythonhosted.org/packages/2a/57/4ecff6d4ec8585342f0c71bc03efaa99cb7468f7c91a57b105bcd561cea8/charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", size = 144610, upload-time = "2026-03-15T18:52:02.213Z" }, + { url = "https://files.pythonhosted.org/packages/80/94/8434a02d9d7f168c25767c64671fead8d599744a05d6a6c877144c754246/charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", size = 154962, upload-time = "2026-03-15T18:52:03.658Z" }, + { url = "https://files.pythonhosted.org/packages/46/4c/48f2cdbfd923026503dfd67ccea45c94fd8fe988d9056b468579c66ed62b/charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", size = 143595, upload-time = "2026-03-15T18:52:05.123Z" }, + { url = "https://files.pythonhosted.org/packages/31/93/8878be7569f87b14f1d52032946131bcb6ebbd8af3e20446bc04053dc3f1/charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", size = 314828, upload-time = "2026-03-15T18:52:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/06/b6/fae511ca98aac69ecc35cde828b0a3d146325dd03d99655ad38fc2cc3293/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", size = 208138, upload-time = "2026-03-15T18:52:08.239Z" }, + { url = "https://files.pythonhosted.org/packages/54/57/64caf6e1bf07274a1e0b7c160a55ee9e8c9ec32c46846ce59b9c333f7008/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", size = 224679, upload-time = "2026-03-15T18:52:10.043Z" }, + { url = "https://files.pythonhosted.org/packages/aa/cb/9ff5a25b9273ef160861b41f6937f86fae18b0792fe0a8e75e06acb08f1d/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", size = 223475, upload-time = "2026-03-15T18:52:11.854Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/440635fc093b8d7347502a377031f9605a1039c958f3cd18dcacffb37743/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", size = 215230, upload-time = "2026-03-15T18:52:13.325Z" }, + { url = "https://files.pythonhosted.org/packages/cd/24/afff630feb571a13f07c8539fbb502d2ab494019492aaffc78ef41f1d1d0/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", size = 199045, upload-time = "2026-03-15T18:52:14.752Z" }, + { url = "https://files.pythonhosted.org/packages/e5/17/d1399ecdaf7e0498c327433e7eefdd862b41236a7e484355b8e0e5ebd64b/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", size = 211658, upload-time = "2026-03-15T18:52:16.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/38/16baa0affb957b3d880e5ac2144caf3f9d7de7bc4a91842e447fbb5e8b67/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", size = 210769, upload-time = "2026-03-15T18:52:17.782Z" }, + { url = "https://files.pythonhosted.org/packages/05/34/c531bc6ac4c21da9ddfddb3107be2287188b3ea4b53b70fc58f2a77ac8d8/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", size = 201328, upload-time = "2026-03-15T18:52:19.553Z" }, + { url = "https://files.pythonhosted.org/packages/fa/73/a5a1e9ca5f234519c1953608a03fe109c306b97fdfb25f09182babad51a7/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", size = 225302, upload-time = "2026-03-15T18:52:21.043Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f6/cd782923d112d296294dea4bcc7af5a7ae0f86ab79f8fefbda5526b6cfc0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659", size = 211127, upload-time = "2026-03-15T18:52:22.491Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c5/0b6898950627af7d6103a449b22320372c24c6feda91aa24e201a478d161/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", size = 222840, upload-time = "2026-03-15T18:52:24.113Z" }, + { url = "https://files.pythonhosted.org/packages/7d/25/c4bba773bef442cbdc06111d40daa3de5050a676fa26e85090fc54dd12f0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", size = 216890, upload-time = "2026-03-15T18:52:25.541Z" }, + { url = "https://files.pythonhosted.org/packages/35/1a/05dacadb0978da72ee287b0143097db12f2e7e8d3ffc4647da07a383b0b7/charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", size = 155379, upload-time = "2026-03-15T18:52:27.05Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7a/d269d834cb3a76291651256f3b9a5945e81d0a49ab9f4a498964e83c0416/charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", size = 169043, upload-time = "2026-03-15T18:52:28.502Z" }, + { url = "https://files.pythonhosted.org/packages/23/06/28b29fba521a37a8932c6a84192175c34d49f84a6d4773fa63d05f9aff22/charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", size = 148523, upload-time = "2026-03-15T18:52:29.956Z" }, + { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, ] [[package]] @@ -476,7 +492,7 @@ wheels = [ [[package]] name = "ddcdatabases" -version = "3.0.12" +version = "4.0.0" source = { editable = "." } dependencies = [ { name = "pydantic-settings" }, @@ -533,7 +549,7 @@ requires-dist = [ { name = "psycopg", extras = ["binary"], marker = "extra == 'postgres'", specifier = ">=3.3.3" }, { name = "pydantic-settings", specifier = ">=2.11.0" }, { name = "pyodbc", marker = "extra == 'mssql'", specifier = ">=5.3.0" }, - { name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0.47" }, + { name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0.48" }, ] provides-extras = ["mongodb", "oracle", "mssql", "mysql", "postgres", "pgsql", "mariadb"] @@ -542,7 +558,7 @@ dev = [ { name = "coverage", specifier = ">=7.13.4" }, { name = "poethepoet", specifier = ">=0.42.1" }, { name = "pytest-asyncio", specifier = ">=1.3.0" }, - { name = "ruff", specifier = ">=0.15.4" }, + { name = "ruff", specifier = ">=0.15.6" }, { name = "testcontainers", extras = ["postgres", "mysql", "mssql", "mongodb", "oracle"], specifier = ">=4.14.1" }, ] @@ -1342,87 +1358,87 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.4" +version = "0.15.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/31/d6e536cdebb6568ae75a7f00e4b4819ae0ad2640c3604c305a0428680b0c/ruff-0.15.4.tar.gz", hash = "sha256:3412195319e42d634470cc97aa9803d07e9d5c9223b99bcb1518f0c725f26ae1", size = 4569550, upload-time = "2026-02-26T20:04:14.959Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/df/f8629c19c5318601d3121e230f74cbee7a3732339c52b21daa2b82ef9c7d/ruff-0.15.6.tar.gz", hash = "sha256:8394c7bb153a4e3811a4ecdacd4a8e6a4fa8097028119160dffecdcdf9b56ae4", size = 4597916, upload-time = "2026-03-12T23:05:47.51Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/82/c11a03cfec3a4d26a0ea1e571f0f44be5993b923f905eeddfc397c13d360/ruff-0.15.4-py3-none-linux_armv6l.whl", hash = "sha256:a1810931c41606c686bae8b5b9a8072adac2f611bb433c0ba476acba17a332e0", size = 10453333, upload-time = "2026-02-26T20:04:20.093Z" }, - { url = "https://files.pythonhosted.org/packages/ce/5d/6a1f271f6e31dffb31855996493641edc3eef8077b883eaf007a2f1c2976/ruff-0.15.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a1632c66672b8b4d3e1d1782859e98d6e0b4e70829530666644286600a33992", size = 10853356, upload-time = "2026-02-26T20:04:05.808Z" }, - { url = "https://files.pythonhosted.org/packages/b1/d8/0fab9f8842b83b1a9c2bf81b85063f65e93fb512e60effa95b0be49bfc54/ruff-0.15.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4386ba2cd6c0f4ff75252845906acc7c7c8e1ac567b7bc3d373686ac8c222ba", size = 10187434, upload-time = "2026-02-26T20:03:54.656Z" }, - { url = "https://files.pythonhosted.org/packages/85/cc/cc220fd9394eff5db8d94dec199eec56dd6c9f3651d8869d024867a91030/ruff-0.15.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2496488bdfd3732747558b6f95ae427ff066d1fcd054daf75f5a50674411e75", size = 10535456, upload-time = "2026-02-26T20:03:52.738Z" }, - { url = "https://files.pythonhosted.org/packages/fa/0f/bced38fa5cf24373ec767713c8e4cadc90247f3863605fb030e597878661/ruff-0.15.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f1c4893841ff2d54cbda1b2860fa3260173df5ddd7b95d370186f8a5e66a4ac", size = 10287772, upload-time = "2026-02-26T20:04:08.138Z" }, - { url = "https://files.pythonhosted.org/packages/2b/90/58a1802d84fed15f8f281925b21ab3cecd813bde52a8ca033a4de8ab0e7a/ruff-0.15.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:820b8766bd65503b6c30aaa6331e8ef3a6e564f7999c844e9a547c40179e440a", size = 11049051, upload-time = "2026-02-26T20:04:03.53Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ac/b7ad36703c35f3866584564dc15f12f91cb1a26a897dc2fd13d7cb3ae1af/ruff-0.15.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9fb74bab47139c1751f900f857fa503987253c3ef89129b24ed375e72873e85", size = 11890494, upload-time = "2026-02-26T20:04:10.497Z" }, - { url = "https://files.pythonhosted.org/packages/93/3d/3eb2f47a39a8b0da99faf9c54d3eb24720add1e886a5309d4d1be73a6380/ruff-0.15.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f80c98765949c518142b3a50a5db89343aa90f2c2bf7799de9986498ae6176db", size = 11326221, upload-time = "2026-02-26T20:04:12.84Z" }, - { url = "https://files.pythonhosted.org/packages/ff/90/bf134f4c1e5243e62690e09d63c55df948a74084c8ac3e48a88468314da6/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451a2e224151729b3b6c9ffb36aed9091b2996fe4bdbd11f47e27d8f2e8888ec", size = 11168459, upload-time = "2026-02-26T20:04:00.969Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e5/a64d27688789b06b5d55162aafc32059bb8c989c61a5139a36e1368285eb/ruff-0.15.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8f157f2e583c513c4f5f896163a93198297371f34c04220daf40d133fdd4f7f", size = 11104366, upload-time = "2026-02-26T20:03:48.099Z" }, - { url = "https://files.pythonhosted.org/packages/f1/f6/32d1dcb66a2559763fc3027bdd65836cad9eb09d90f2ed6a63d8e9252b02/ruff-0.15.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:917cc68503357021f541e69b35361c99387cdbbf99bd0ea4aa6f28ca99ff5338", size = 10510887, upload-time = "2026-02-26T20:03:45.771Z" }, - { url = "https://files.pythonhosted.org/packages/ff/92/22d1ced50971c5b6433aed166fcef8c9343f567a94cf2b9d9089f6aa80fe/ruff-0.15.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e9737c8161da79fd7cfec19f1e35620375bd8b2a50c3e77fa3d2c16f574105cc", size = 10285939, upload-time = "2026-02-26T20:04:22.42Z" }, - { url = "https://files.pythonhosted.org/packages/e6/f4/7c20aec3143837641a02509a4668fb146a642fd1211846634edc17eb5563/ruff-0.15.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:291258c917539e18f6ba40482fe31d6f5ac023994ee11d7bdafd716f2aab8a68", size = 10765471, upload-time = "2026-02-26T20:03:58.924Z" }, - { url = "https://files.pythonhosted.org/packages/d0/09/6d2f7586f09a16120aebdff8f64d962d7c4348313c77ebb29c566cefc357/ruff-0.15.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3f83c45911da6f2cd5936c436cf86b9f09f09165f033a99dcf7477e34041cbc3", size = 11263382, upload-time = "2026-02-26T20:04:24.424Z" }, - { url = "https://files.pythonhosted.org/packages/1b/fa/2ef715a1cd329ef47c1a050e10dee91a9054b7ce2fcfdd6a06d139afb7ec/ruff-0.15.4-py3-none-win32.whl", hash = "sha256:65594a2d557d4ee9f02834fcdf0a28daa8b3b9f6cb2cb93846025a36db47ef22", size = 10506664, upload-time = "2026-02-26T20:03:50.56Z" }, - { url = "https://files.pythonhosted.org/packages/d0/a8/c688ef7e29983976820d18710f955751d9f4d4eb69df658af3d006e2ba3e/ruff-0.15.4-py3-none-win_amd64.whl", hash = "sha256:04196ad44f0df220c2ece5b0e959c2f37c777375ec744397d21d15b50a75264f", size = 11651048, upload-time = "2026-02-26T20:04:17.191Z" }, - { url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2f/4e03a7e5ce99b517e98d3b4951f411de2b0fa8348d39cf446671adcce9a2/ruff-0.15.6-py3-none-linux_armv6l.whl", hash = "sha256:7c98c3b16407b2cf3d0f2b80c80187384bc92c6774d85fefa913ecd941256fff", size = 10508953, upload-time = "2026-03-12T23:05:17.246Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/55bcdc3e9f80bcf39edf0cd272da6fa511a3d94d5a0dd9e0adf76ceebdb4/ruff-0.15.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee7dcfaad8b282a284df4aa6ddc2741b3f4a18b0555d626805555a820ea181c3", size = 10942257, upload-time = "2026-03-12T23:05:23.076Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/005c29bd1726c0f492bfa215e95154cf480574140cb5f867c797c18c790b/ruff-0.15.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3bd9967851a25f038fc8b9ae88a7fbd1b609f30349231dffaa37b6804923c4bb", size = 10322683, upload-time = "2026-03-12T23:05:33.738Z" }, + { url = "https://files.pythonhosted.org/packages/5f/74/2f861f5fd7cbb2146bddb5501450300ce41562da36d21868c69b7a828169/ruff-0.15.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13f4594b04e42cd24a41da653886b04d2ff87adbf57497ed4f728b0e8a4866f8", size = 10660986, upload-time = "2026-03-12T23:05:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/c1/a1/309f2364a424eccb763cdafc49df843c282609f47fe53aa83f38272389e0/ruff-0.15.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2ed8aea2f3fe57886d3f00ea5b8aae5bf68d5e195f487f037a955ff9fbaac9e", size = 10332177, upload-time = "2026-03-12T23:05:56.145Z" }, + { url = "https://files.pythonhosted.org/packages/30/41/7ebf1d32658b4bab20f8ac80972fb19cd4e2c6b78552be263a680edc55ac/ruff-0.15.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70789d3e7830b848b548aae96766431c0dc01a6c78c13381f423bf7076c66d15", size = 11170783, upload-time = "2026-03-12T23:06:01.742Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/6d488f6adca047df82cd62c304638bcb00821c36bd4881cfca221561fdfc/ruff-0.15.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:542aaf1de3154cea088ced5a819ce872611256ffe2498e750bbae5247a8114e9", size = 12044201, upload-time = "2026-03-12T23:05:28.697Z" }, + { url = "https://files.pythonhosted.org/packages/71/68/e6f125df4af7e6d0b498f8d373274794bc5156b324e8ab4bf5c1b4fc0ec7/ruff-0.15.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c22e6f02c16cfac3888aa636e9eba857254d15bbacc9906c9689fdecb1953ab", size = 11421561, upload-time = "2026-03-12T23:05:31.236Z" }, + { url = "https://files.pythonhosted.org/packages/f1/9f/f85ef5fd01a52e0b472b26dc1b4bd228b8f6f0435975442ffa4741278703/ruff-0.15.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98893c4c0aadc8e448cfa315bd0cc343a5323d740fe5f28ef8a3f9e21b381f7e", size = 11310928, upload-time = "2026-03-12T23:05:45.288Z" }, + { url = "https://files.pythonhosted.org/packages/8c/26/b75f8c421f5654304b89471ed384ae8c7f42b4dff58fa6ce1626d7f2b59a/ruff-0.15.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:70d263770d234912374493e8cc1e7385c5d49376e41dfa51c5c3453169dc581c", size = 11235186, upload-time = "2026-03-12T23:05:50.677Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/d5a6d065962ff7a68a86c9b4f5500f7d101a0792078de636526c0edd40da/ruff-0.15.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:55a1ad63c5a6e54b1f21b7514dfadc0c7fb40093fa22e95143cf3f64ebdcd512", size = 10635231, upload-time = "2026-03-12T23:05:37.044Z" }, + { url = "https://files.pythonhosted.org/packages/d6/56/7c3acf3d50910375349016cf33de24be021532042afbed87942858992491/ruff-0.15.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8dc473ba093c5ec238bb1e7429ee676dca24643c471e11fbaa8a857925b061c0", size = 10340357, upload-time = "2026-03-12T23:06:04.748Z" }, + { url = "https://files.pythonhosted.org/packages/06/54/6faa39e9c1033ff6a3b6e76b5df536931cd30caf64988e112bbf91ef5ce5/ruff-0.15.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:85b042377c2a5561131767974617006f99f7e13c63c111b998f29fc1e58a4cfb", size = 10860583, upload-time = "2026-03-12T23:05:58.978Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/509a201b843b4dfb0b32acdedf68d951d3377988cae43949ba4c4133a96a/ruff-0.15.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cef49e30bc5a86a6a92098a7fbf6e467a234d90b63305d6f3ec01225a9d092e0", size = 11410976, upload-time = "2026-03-12T23:05:39.955Z" }, + { url = "https://files.pythonhosted.org/packages/6c/25/3fc9114abf979a41673ce877c08016f8e660ad6cf508c3957f537d2e9fa9/ruff-0.15.6-py3-none-win32.whl", hash = "sha256:bbf67d39832404812a2d23020dda68fee7f18ce15654e96fb1d3ad21a5fe436c", size = 10616872, upload-time = "2026-03-12T23:05:42.451Z" }, + { url = "https://files.pythonhosted.org/packages/89/7a/09ece68445ceac348df06e08bf75db72d0e8427765b96c9c0ffabc1be1d9/ruff-0.15.6-py3-none-win_amd64.whl", hash = "sha256:aee25bc84c2f1007ecb5037dff75cef00414fdf17c23f07dc13e577883dca406", size = 11787271, upload-time = "2026-03-12T23:05:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497, upload-time = "2026-03-12T23:05:25.968Z" }, ] [[package]] name = "sqlalchemy" -version = "2.0.47" +version = "2.0.48" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/4b/1e00561093fe2cd8eef09d406da003c8a118ff02d6548498c1ae677d68d9/sqlalchemy-2.0.47.tar.gz", hash = "sha256:e3e7feb57b267fe897e492b9721ae46d5c7de6f9e8dee58aacf105dc4e154f3d", size = 9886323, upload-time = "2026-02-24T16:34:27.947Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/73/b4a9737255583b5fa858e0bb8e116eb94b88c910164ed2ed719147bde3de/sqlalchemy-2.0.48.tar.gz", hash = "sha256:5ca74f37f3369b45e1f6b7b06afb182af1fd5dde009e4ffd831830d98cbe5fe7", size = 9886075, upload-time = "2026-03-02T15:28:51.474Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/75/17db77c57129c223c7d98518ad1e1faa24ee350c22a44b55390d8463c28c/sqlalchemy-2.0.47-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33a917ede39406ddb93c3e642b5bc480be7c5fd0f3d0d6ae1036d466fb963f1a", size = 2157331, upload-time = "2026-02-24T16:43:52.693Z" }, - { url = "https://files.pythonhosted.org/packages/b0/d6/3658f7e5c376de774c009f2bb9c0ddf88a35b89c5bfb15ee7174a17b1a5f/sqlalchemy-2.0.47-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:561d027c829b01e040bdade6b6f5b429249d056ef95d7bdcb9211539ecc82803", size = 3236939, upload-time = "2026-02-24T17:28:57.419Z" }, - { url = "https://files.pythonhosted.org/packages/4e/38/f4b94f85d1c26cb9ee0e57449754de816c326f9586b9a8c5247eb49146de/sqlalchemy-2.0.47-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fa5072a37e68c565363c009b7afa5b199b488c87940ec02719860093a08f34ca", size = 3235190, upload-time = "2026-02-24T17:27:07.884Z" }, - { url = "https://files.pythonhosted.org/packages/94/f2/36714f1de01e135a2bf142b662e416e5338ab63c47878e31051338c66e2d/sqlalchemy-2.0.47-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1e7ed17dd4312a298b6024bfd1baf51654bc49e3f03c798005babf0c7922d6a7", size = 3188064, upload-time = "2026-02-24T17:28:58.908Z" }, - { url = "https://files.pythonhosted.org/packages/ab/94/fcd978e7625cd1c97d9f1d7363e18e37d24314e572acd7c091e3a4210106/sqlalchemy-2.0.47-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6992e353fcb0593eb42d95ad84b3e58fe40b5e37fd332b9ccba28f4b2f36d1fc", size = 3209480, upload-time = "2026-02-24T17:27:09.823Z" }, - { url = "https://files.pythonhosted.org/packages/23/29/c633202b9900ab65f0162f59df737b57f30010f44d892b186810c9ed58b7/sqlalchemy-2.0.47-cp310-cp310-win32.whl", hash = "sha256:05a6d58ed99ebd01303c92d29a0c9cbf70f637b3ddd155f5172c5a7239940998", size = 2117652, upload-time = "2026-02-24T17:14:34.635Z" }, - { url = "https://files.pythonhosted.org/packages/00/39/54acf13913932b8508058d47a169e6fcde9adaa4cbfa16cbf30da1f6a482/sqlalchemy-2.0.47-cp310-cp310-win_amd64.whl", hash = "sha256:4a7aa4a584cc97e268c11e700dea0b763874eaebb435e75e7d0ffee5d90f5030", size = 2140883, upload-time = "2026-02-24T17:14:35.875Z" }, - { url = "https://files.pythonhosted.org/packages/94/13/886338d3e8ab5ddcfe84d54302c749b1793e16c4bba63d7004e3f7baa8ec/sqlalchemy-2.0.47-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a1dbf0913879c443617d6b64403cf2801c941651db8c60e96d204ed9388d6b0", size = 2157124, upload-time = "2026-02-24T16:43:54.706Z" }, - { url = "https://files.pythonhosted.org/packages/b6/bb/a897f6a66c9986aa9f27f5cf8550637d8a5ea368fd7fb42f6dac3105b4dc/sqlalchemy-2.0.47-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:775effbb97ea3b00c4dd3aeaf3ba8acba6e3e2b4b41d17d67a27e696843dbc95", size = 3313513, upload-time = "2026-02-24T17:29:00.527Z" }, - { url = "https://files.pythonhosted.org/packages/59/fb/69bfae022b681507565ab0d34f0c80aa1e9f954a5a7cbfb0ed054966ac8d/sqlalchemy-2.0.47-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56cc834a3ffac34270cc2a41875e0f40e97aa651f4f3ca1cfbbf421c044cb62b", size = 3313014, upload-time = "2026-02-24T17:27:11.679Z" }, - { url = "https://files.pythonhosted.org/packages/04/f3/0eba329f7c182d53205a228c4fd24651b95489b431ea2bd830887b4c13c4/sqlalchemy-2.0.47-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49b5e0c7244262f39e767c018e4fdb5e5dbc23cd54c5ddac8eea8f0ba32ef890", size = 3265389, upload-time = "2026-02-24T17:29:02.497Z" }, - { url = "https://files.pythonhosted.org/packages/5c/06/654edc084b3b46ac79e04200d7c46467ae80c759c4ee41c897f9272b036f/sqlalchemy-2.0.47-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15cd822a3f1f6f77b5b841a30c1a07a07f7dee3385f17e638e1722de9ab683be", size = 3287604, upload-time = "2026-02-24T17:27:13.295Z" }, - { url = "https://files.pythonhosted.org/packages/78/33/c18c8f63b61981219d3aa12321bb7ccee605034d195e868ed94f9727b27c/sqlalchemy-2.0.47-cp311-cp311-win32.whl", hash = "sha256:9847a19548cd283a65e1ce0afd54016598d55ff72682d6fd3e493af6fc044064", size = 2116916, upload-time = "2026-02-24T17:14:37.392Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c6/a59e3f9796fff844e16afbd821db9abfd6e12698db9441a231a96193a100/sqlalchemy-2.0.47-cp311-cp311-win_amd64.whl", hash = "sha256:722abf1c82aeca46a1a0803711244a48a298279eeaec9e02f7bfee9e064182e5", size = 2141587, upload-time = "2026-02-24T17:14:39.746Z" }, - { url = "https://files.pythonhosted.org/packages/80/88/74eb470223ff88ea6572a132c0b8de8c1d8ed7b843d3b44a8a3c77f31d39/sqlalchemy-2.0.47-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fa91b19d6b9821c04cc8f7aa2476429cc8887b9687c762815aa629f5c0edec1", size = 2155687, upload-time = "2026-02-24T17:05:46.451Z" }, - { url = "https://files.pythonhosted.org/packages/ef/ba/1447d3d558971b036cb93b557595cb5dcdfe728f1c7ac4dec16505ef5756/sqlalchemy-2.0.47-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c5bbbd14eff577c8c79cbfe39a0771eecd20f430f3678533476f0087138f356", size = 3336978, upload-time = "2026-02-24T17:18:04.597Z" }, - { url = "https://files.pythonhosted.org/packages/8a/07/b47472d2ffd0776826f17ccf0b4d01b224c99fbd1904aeb103dffbb4b1cc/sqlalchemy-2.0.47-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a6c555da8d4280a3c4c78c5b7a3f990cee2b2884e5f934f87a226191682ff7", size = 3349939, upload-time = "2026-02-24T17:27:18.937Z" }, - { url = "https://files.pythonhosted.org/packages/bb/c6/95fa32b79b57769da3e16f054cf658d90940317b5ca0ec20eac84aa19c4f/sqlalchemy-2.0.47-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ed48a1701d24dff3bb49a5bce94d6bc84cbe33d98af2aa2d3cdcce3dea1709ec", size = 3279648, upload-time = "2026-02-24T17:18:07.038Z" }, - { url = "https://files.pythonhosted.org/packages/bb/c8/3d07e7c73928dc59a0bed40961ca4e313e797bce650b088e8d5fdd3ad939/sqlalchemy-2.0.47-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f3178c920ad98158f0b6309382194df04b14808fa6052ae07099fdde29d5602", size = 3314695, upload-time = "2026-02-24T17:27:20.93Z" }, - { url = "https://files.pythonhosted.org/packages/6b/d2/ed32b1611c1e19fdb028eee1adc5a9aa138c2952d09ae11f1670170f80ae/sqlalchemy-2.0.47-cp312-cp312-win32.whl", hash = "sha256:b9c11ac9934dd59ece9619fe42780a08abe2faab7b0543bb00d5eabea4f421b9", size = 2115502, upload-time = "2026-02-24T17:22:52.546Z" }, - { url = "https://files.pythonhosted.org/packages/fd/52/9de590356a4dd8e9ef5a881dbba64b2bbc4cbc71bf02bc68e775fb9b1899/sqlalchemy-2.0.47-cp312-cp312-win_amd64.whl", hash = "sha256:db43b72cf8274a99e089755c9c1e0b947159b71adbc2c83c3de2e38d5d607acb", size = 2142435, upload-time = "2026-02-24T17:22:54.268Z" }, - { url = "https://files.pythonhosted.org/packages/4a/e5/0af64ce7d8f60ec5328c10084e2f449e7912a9b8bdbefdcfb44454a25f49/sqlalchemy-2.0.47-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:456a135b790da5d3c6b53d0ef71ac7b7d280b7f41eb0c438986352bf03ca7143", size = 2152551, upload-time = "2026-02-24T17:05:47.675Z" }, - { url = "https://files.pythonhosted.org/packages/63/79/746b8d15f6940e2ac469ce22d7aa5b1124b1ab820bad9b046eb3000c88a6/sqlalchemy-2.0.47-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09a2f7698e44b3135433387da5d8846cf7cc7c10e5425af7c05fee609df978b6", size = 3278782, upload-time = "2026-02-24T17:18:10.012Z" }, - { url = "https://files.pythonhosted.org/packages/91/b1/bd793ddb34345d1ed43b13ab2d88c95d7d4eb2e28f5b5a99128b9cc2bca2/sqlalchemy-2.0.47-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bbc72e6a177c78d724f9106aaddc0d26a2ada89c6332b5935414eccf04cbd5", size = 3295155, upload-time = "2026-02-24T17:27:22.827Z" }, - { url = "https://files.pythonhosted.org/packages/97/84/7213def33f94e5ca6f5718d259bc9f29de0363134648425aa218d4356b23/sqlalchemy-2.0.47-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:75460456b043b78b6006e41bdf5b86747ee42eafaf7fffa3b24a6e9a456a2092", size = 3226834, upload-time = "2026-02-24T17:18:11.465Z" }, - { url = "https://files.pythonhosted.org/packages/ef/06/456810204f4dc29b5f025b1b0a03b4bd6b600ebf3c1040aebd90a257fa33/sqlalchemy-2.0.47-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d9adaa616c3bc7d80f9ded57cd84b51d6617cad6a5456621d858c9f23aaee01", size = 3265001, upload-time = "2026-02-24T17:27:24.813Z" }, - { url = "https://files.pythonhosted.org/packages/fb/20/df3920a4b2217dbd7390a5bd277c1902e0393f42baaf49f49b3c935e7328/sqlalchemy-2.0.47-cp313-cp313-win32.whl", hash = "sha256:76e09f974382a496a5ed985db9343628b1cb1ac911f27342e4cc46a8bac10476", size = 2113647, upload-time = "2026-02-24T17:22:55.747Z" }, - { url = "https://files.pythonhosted.org/packages/46/06/7873ddf69918efbfabd7211829f4bd8019739d0a719253112d305d3ba51d/sqlalchemy-2.0.47-cp313-cp313-win_amd64.whl", hash = "sha256:0664089b0bf6724a0bfb49a0cf4d4da24868a0a5c8e937cd7db356d5dcdf2c66", size = 2139425, upload-time = "2026-02-24T17:22:57.033Z" }, - { url = "https://files.pythonhosted.org/packages/54/fa/61ad9731370c90ac7ea5bf8f5eaa12c48bb4beec41c0fa0360becf4ac10d/sqlalchemy-2.0.47-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed0c967c701ae13da98eb220f9ddab3044ab63504c1ba24ad6a59b26826ad003", size = 3558809, upload-time = "2026-02-24T17:12:15.232Z" }, - { url = "https://files.pythonhosted.org/packages/33/d5/221fac96f0529391fe374875633804c866f2b21a9c6d3a6ca57d9c12cfd7/sqlalchemy-2.0.47-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3537943a61fd25b241e976426a0c6814434b93cf9b09d39e8e78f3c9eb9a487", size = 3525480, upload-time = "2026-02-24T17:27:59.602Z" }, - { url = "https://files.pythonhosted.org/packages/ec/55/8247d53998c3673e4a8d1958eba75c6f5cc3b39082029d400bb1f2a911ae/sqlalchemy-2.0.47-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:57f7e336a64a0dba686c66392d46b9bc7af2c57d55ce6dc1697b4ef32b043ceb", size = 3466569, upload-time = "2026-02-24T17:12:16.94Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b5/c1f0eea1bac6790845f71420a7fe2f2a0566203aa57543117d4af3b77d1c/sqlalchemy-2.0.47-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dff735a621858680217cb5142b779bad40ef7322ddbb7c12062190db6879772e", size = 3475770, upload-time = "2026-02-24T17:28:02.034Z" }, - { url = "https://files.pythonhosted.org/packages/c5/ed/2f43f92474ea0c43c204657dc47d9d002cd738b96ca2af8e6d29a9b5e42d/sqlalchemy-2.0.47-cp313-cp313t-win32.whl", hash = "sha256:3893dc096bb3cca9608ea3487372ffcea3ae9b162f40e4d3c51dd49db1d1b2dc", size = 2141300, upload-time = "2026-02-24T17:14:37.024Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a9/8b73f9f1695b6e92f7aaf1711135a1e3bbeb78bca9eded35cb79180d3c6d/sqlalchemy-2.0.47-cp313-cp313t-win_amd64.whl", hash = "sha256:b5103427466f4b3e61f04833ae01f9a914b1280a2a8bcde3a9d7ab11f3755b42", size = 2173053, upload-time = "2026-02-24T17:14:38.688Z" }, - { url = "https://files.pythonhosted.org/packages/c1/30/98243209aae58ed80e090ea988d5182244ca7ab3ff59e6d850c3dfc7651e/sqlalchemy-2.0.47-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b03010a5a5dfe71676bc83f2473ebe082478e32d77e6f082c8fe15a31c3b42a6", size = 2154355, upload-time = "2026-02-24T17:05:48.959Z" }, - { url = "https://files.pythonhosted.org/packages/ab/62/12ca6ea92055fe486d6558a2a4efe93e194ff597463849c01f88e5adb99d/sqlalchemy-2.0.47-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8e3371aa9024520883a415a09cc20c33cfd3eeccf9e0f4f4c367f940b9cbd44", size = 3274486, upload-time = "2026-02-24T17:18:13.659Z" }, - { url = "https://files.pythonhosted.org/packages/97/88/7dfbdeaa8d42b1584e65d6cc713e9d33b6fa563e0d546d5cb87e545bb0e5/sqlalchemy-2.0.47-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9449f747e50d518c6e1b40cc379e48bfc796453c47b15e627ea901c201e48a6", size = 3279481, upload-time = "2026-02-24T17:27:26.491Z" }, - { url = "https://files.pythonhosted.org/packages/d0/b7/75e1c1970616a9dd64a8a6fd788248da2ddaf81c95f4875f2a1e8aee4128/sqlalchemy-2.0.47-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:21410f60d5cac1d6bfe360e05bd91b179be4fa0aa6eea6be46054971d277608f", size = 3224269, upload-time = "2026-02-24T17:18:15.078Z" }, - { url = "https://files.pythonhosted.org/packages/31/ac/eec1a13b891df9a8bc203334caf6e6aac60b02f61b018ef3b4124b8c4120/sqlalchemy-2.0.47-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:819841dd5bb4324c284c09e2874cf96fe6338bfb57a64548d9b81a4e39c9871f", size = 3246262, upload-time = "2026-02-24T17:27:27.986Z" }, - { url = "https://files.pythonhosted.org/packages/c9/b0/661b0245b06421058610da39f8ceb34abcc90b49f90f256380968d761dbe/sqlalchemy-2.0.47-cp314-cp314-win32.whl", hash = "sha256:e255ee44821a7ef45649c43064cf94e74f81f61b4df70547304b97a351e9b7db", size = 2116528, upload-time = "2026-02-24T17:22:59.363Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ef/1035a90d899e61810791c052004958be622a2cf3eb3df71c3fe20778c5d0/sqlalchemy-2.0.47-cp314-cp314-win_amd64.whl", hash = "sha256:209467ff73ea1518fe1a5aaed9ba75bb9e33b2666e2553af9ccd13387bf192cb", size = 2142181, upload-time = "2026-02-24T17:23:01.001Z" }, - { url = "https://files.pythonhosted.org/packages/76/bb/17a1dd09cbba91258218ceb582225f14b5364d2683f9f5a274f72f2d764f/sqlalchemy-2.0.47-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e78fd9186946afaa287f8a1fe147ead06e5d566b08c0afcb601226e9c7322a64", size = 3563477, upload-time = "2026-02-24T17:12:18.46Z" }, - { url = "https://files.pythonhosted.org/packages/66/8f/1a03d24c40cc321ef2f2231f05420d140bb06a84f7047eaa7eaa21d230ba/sqlalchemy-2.0.47-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5740e2f31b5987ed9619d6912ae5b750c03637f2078850da3002934c9532f172", size = 3528568, upload-time = "2026-02-24T17:28:03.732Z" }, - { url = "https://files.pythonhosted.org/packages/fd/53/d56a213055d6b038a5384f0db5ece7343334aca230ff3f0fa1561106f22c/sqlalchemy-2.0.47-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb9ac00d03de93acb210e8ec7243fefe3e012515bf5fd2f0898c8dff38bc77a4", size = 3472284, upload-time = "2026-02-24T17:12:20.319Z" }, - { url = "https://files.pythonhosted.org/packages/ff/19/c235d81b9cfdd6130bf63143b7bade0dc4afa46c4b634d5d6b2a96bea233/sqlalchemy-2.0.47-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c72a0b9eb2672d70d112cb149fbaf172d466bc691014c496aaac594f1988e706", size = 3478410, upload-time = "2026-02-24T17:28:05.892Z" }, - { url = "https://files.pythonhosted.org/packages/0e/db/cafdeca5ecdaa3bb0811ba5449501da677ce0d83be8d05c5822da72d2e86/sqlalchemy-2.0.47-cp314-cp314t-win32.whl", hash = "sha256:c200db1128d72a71dc3c31c24b42eb9fd85b2b3e5a3c9ba1e751c11ac31250ff", size = 2147164, upload-time = "2026-02-24T17:14:40.783Z" }, - { url = "https://files.pythonhosted.org/packages/fc/5e/ff41a010e9e0f76418b02ad352060a4341bb15f0af66cedc924ab376c7c6/sqlalchemy-2.0.47-cp314-cp314t-win_amd64.whl", hash = "sha256:669837759b84e575407355dcff912835892058aea9b80bd1cb76d6a151cf37f7", size = 2182154, upload-time = "2026-02-24T17:14:43.205Z" }, - { url = "https://files.pythonhosted.org/packages/15/9f/7c378406b592fcf1fc157248607b495a40e3202ba4a6f1372a2ba6447717/sqlalchemy-2.0.47-py3-none-any.whl", hash = "sha256:e2647043599297a1ef10e720cf310846b7f31b6c841fee093d2b09d81215eb93", size = 1940159, upload-time = "2026-02-24T17:15:07.158Z" }, + { url = "https://files.pythonhosted.org/packages/9a/67/1235676e93dd3b742a4a8eddfae49eea46c85e3eed29f0da446a8dd57500/sqlalchemy-2.0.48-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7001dc9d5f6bb4deb756d5928eaefe1930f6f4179da3924cbd95ee0e9f4dce89", size = 2157384, upload-time = "2026-03-02T15:38:26.781Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d7/fa728b856daa18c10e1390e76f26f64ac890c947008284387451d56ca3d0/sqlalchemy-2.0.48-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a89ce07ad2d4b8cfc30bd5889ec40613e028ed80ef47da7d9dd2ce969ad30e0", size = 3236981, upload-time = "2026-03-02T15:58:53.53Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ad/6c4395649a212a6c603a72c5b9ab5dce3135a1546cfdffa3c427e71fd535/sqlalchemy-2.0.48-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10853a53a4a00417a00913d270dddda75815fcb80675874285f41051c094d7dd", size = 3235232, upload-time = "2026-03-02T15:52:25.654Z" }, + { url = "https://files.pythonhosted.org/packages/01/f4/58f845e511ac0509765a6f85eb24924c1ef0d54fb50de9d15b28c3601458/sqlalchemy-2.0.48-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fac0fa4e4f55f118fd87177dacb1c6522fe39c28d498d259014020fec9164c29", size = 3188106, upload-time = "2026-03-02T15:58:55.193Z" }, + { url = "https://files.pythonhosted.org/packages/3f/f9/6dcc7bfa5f5794c3a095e78cd1de8269dfb5584dfd4c2c00a50d3c1ade44/sqlalchemy-2.0.48-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3713e21ea67bca727eecd4a24bf68bcd414c403faae4989442be60994301ded0", size = 3209522, upload-time = "2026-03-02T15:52:27.407Z" }, + { url = "https://files.pythonhosted.org/packages/d7/5a/b632875ab35874d42657f079529f0745410604645c269a8c21fb4272ff7a/sqlalchemy-2.0.48-cp310-cp310-win32.whl", hash = "sha256:d404dc897ce10e565d647795861762aa2d06ca3f4a728c5e9a835096c7059018", size = 2117695, upload-time = "2026-03-02T15:46:51.389Z" }, + { url = "https://files.pythonhosted.org/packages/de/03/9752eb2a41afdd8568e41ac3c3128e32a0a73eada5ab80483083604a56d1/sqlalchemy-2.0.48-cp310-cp310-win_amd64.whl", hash = "sha256:841a94c66577661c1f088ac958cd767d7c9bf507698f45afffe7a4017049de76", size = 2140928, upload-time = "2026-03-02T15:46:52.992Z" }, + { url = "https://files.pythonhosted.org/packages/d7/6d/b8b78b5b80f3c3ab3f7fa90faa195ec3401f6d884b60221260fd4d51864c/sqlalchemy-2.0.48-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b4c575df7368b3b13e0cebf01d4679f9a28ed2ae6c1cd0b1d5beffb6b2007dc", size = 2157184, upload-time = "2026-03-02T15:38:28.161Z" }, + { url = "https://files.pythonhosted.org/packages/21/4b/4f3d4a43743ab58b95b9ddf5580a265b593d017693df9e08bd55780af5bb/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e83e3f959aaa1c9df95c22c528096d94848a1bc819f5d0ebf7ee3df0ca63db6c", size = 3313555, upload-time = "2026-03-02T15:58:57.21Z" }, + { url = "https://files.pythonhosted.org/packages/21/dd/3b7c53f1dbbf736fd27041aee68f8ac52226b610f914085b1652c2323442/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f7b7243850edd0b8b97043f04748f31de50cf426e939def5c16bedb540698f7", size = 3313057, upload-time = "2026-03-02T15:52:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/cc/3e600a90ae64047f33313d7d32e5ad025417f09d2ded487e8284b5e21a15/sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:82745b03b4043e04600a6b665cb98697c4339b24e34d74b0a2ac0a2488b6f94d", size = 3265431, upload-time = "2026-03-02T15:58:59.096Z" }, + { url = "https://files.pythonhosted.org/packages/8b/19/780138dacfe3f5024f4cf96e4005e91edf6653d53d3673be4844578faf1d/sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5e088bf43f6ee6fec7dbf1ef7ff7774a616c236b5c0cb3e00662dd71a56b571", size = 3287646, upload-time = "2026-03-02T15:52:31.569Z" }, + { url = "https://files.pythonhosted.org/packages/40/fd/f32ced124f01a23151f4777e4c705f3a470adc7bd241d9f36a7c941a33bf/sqlalchemy-2.0.48-cp311-cp311-win32.whl", hash = "sha256:9c7d0a77e36b5f4b01ca398482230ab792061d243d715299b44a0b55c89fe617", size = 2116956, upload-time = "2026-03-02T15:46:54.535Z" }, + { url = "https://files.pythonhosted.org/packages/58/d5/dd767277f6feef12d05651538f280277e661698f617fa4d086cce6055416/sqlalchemy-2.0.48-cp311-cp311-win_amd64.whl", hash = "sha256:583849c743e0e3c9bb7446f5b5addeacedc168d657a69b418063dfdb2d90081c", size = 2141627, upload-time = "2026-03-02T15:46:55.849Z" }, + { url = "https://files.pythonhosted.org/packages/ef/91/a42ae716f8925e9659df2da21ba941f158686856107a61cc97a95e7647a3/sqlalchemy-2.0.48-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:348174f228b99f33ca1f773e85510e08927620caa59ffe7803b37170df30332b", size = 2155737, upload-time = "2026-03-02T15:49:13.207Z" }, + { url = "https://files.pythonhosted.org/packages/b9/52/f75f516a1f3888f027c1cfb5d22d4376f4b46236f2e8669dcb0cddc60275/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53667b5f668991e279d21f94ccfa6e45b4e3f4500e7591ae59a8012d0f010dcb", size = 3337020, upload-time = "2026-03-02T15:50:34.547Z" }, + { url = "https://files.pythonhosted.org/packages/37/9a/0c28b6371e0cdcb14f8f1930778cb3123acfcbd2c95bb9cf6b4a2ba0cce3/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34634e196f620c7a61d18d5cf7dc841ca6daa7961aed75d532b7e58b309ac894", size = 3349983, upload-time = "2026-03-02T15:53:25.542Z" }, + { url = "https://files.pythonhosted.org/packages/1c/46/0aee8f3ff20b1dcbceb46ca2d87fcc3d48b407925a383ff668218509d132/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:546572a1793cc35857a2ffa1fe0e58571af1779bcc1ffa7c9fb0839885ed69a9", size = 3279690, upload-time = "2026-03-02T15:50:36.277Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/a957bc91293b49181350bfd55e6dfc6e30b7f7d83dc6792d72043274a390/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07edba08061bc277bfdc772dd2a1a43978f5a45994dd3ede26391b405c15221e", size = 3314738, upload-time = "2026-03-02T15:53:27.519Z" }, + { url = "https://files.pythonhosted.org/packages/4b/44/1d257d9f9556661e7bdc83667cc414ba210acfc110c82938cb3611eea58f/sqlalchemy-2.0.48-cp312-cp312-win32.whl", hash = "sha256:908a3fa6908716f803b86896a09a2c4dde5f5ce2bb07aacc71ffebb57986ce99", size = 2115546, upload-time = "2026-03-02T15:54:31.591Z" }, + { url = "https://files.pythonhosted.org/packages/f2/af/c3c7e1f3a2b383155a16454df62ae8c62a30dd238e42e68c24cebebbfae6/sqlalchemy-2.0.48-cp312-cp312-win_amd64.whl", hash = "sha256:68549c403f79a8e25984376480959975212a670405e3913830614432b5daa07a", size = 2142484, upload-time = "2026-03-02T15:54:34.072Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e3070c03701037aa418b55d36532ecb8f8446ed0135acb71c678dbdf12f5b6e4", size = 2152599, upload-time = "2026-03-02T15:49:14.41Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ff/f4e04a4bd5a24304f38cb0d4aa2ad4c0fb34999f8b884c656535e1b2b74c/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2645b7d8a738763b664a12a1542c89c940daa55196e8d73e55b169cc5c99f65f", size = 3278825, upload-time = "2026-03-02T15:50:38.269Z" }, + { url = "https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b19151e76620a412c2ac1c6f977ab1b9fa7ad43140178345136456d5265b32ed", size = 3295200, upload-time = "2026-03-02T15:53:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/87/dc/1609a4442aefd750ea2f32629559394ec92e89ac1d621a7f462b70f736ff/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b193a7e29fd9fa56e502920dca47dffe60f97c863494946bd698c6058a55658", size = 3226876, upload-time = "2026-03-02T15:50:39.802Z" }, + { url = "https://files.pythonhosted.org/packages/37/c3/6ae2ab5ea2fa989fbac4e674de01224b7a9d744becaf59bb967d62e99bed/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:36ac4ddc3d33e852da9cb00ffb08cea62ca05c39711dc67062ca2bb1fae35fd8", size = 3265045, upload-time = "2026-03-02T15:53:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/6f/82/ea4665d1bb98c50c19666e672f21b81356bd6077c4574e3d2bbb84541f53/sqlalchemy-2.0.48-cp313-cp313-win32.whl", hash = "sha256:389b984139278f97757ea9b08993e7b9d1142912e046ab7d82b3fbaeb0209131", size = 2113700, upload-time = "2026-03-02T15:54:35.825Z" }, + { url = "https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl", hash = "sha256:d612c976cbc2d17edfcc4c006874b764e85e990c29ce9bd411f926bbfb02b9a2", size = 2139487, upload-time = "2026-03-02T15:54:37.079Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/7b17bd50244b78a49d22cc63c969d71dc4de54567dc152a9b46f6fae40ce/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69f5bc24904d3bc3640961cddd2523e361257ef68585d6e364166dfbe8c78fae", size = 3558851, upload-time = "2026-03-02T15:57:48.607Z" }, + { url = "https://files.pythonhosted.org/packages/20/0d/213668e9aca61d370f7d2a6449ea4ec699747fac67d4bda1bb3d129025be/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd08b90d211c086181caed76931ecfa2bdfc83eea3cfccdb0f82abc6c4b876cb", size = 3525525, upload-time = "2026-03-02T16:04:38.058Z" }, + { url = "https://files.pythonhosted.org/packages/85/d7/a84edf412979e7d59c69b89a5871f90a49228360594680e667cb2c46a828/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1ccd42229aaac2df431562117ac7e667d702e8e44afdb6cf0e50fa3f18160f0b", size = 3466611, upload-time = "2026-03-02T15:57:50.759Z" }, + { url = "https://files.pythonhosted.org/packages/86/55/42404ce5770f6be26a2b0607e7866c31b9a4176c819e9a7a5e0a055770be/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0dcbc588cd5b725162c076eb9119342f6579c7f7f55057bb7e3c6ff27e13121", size = 3475812, upload-time = "2026-03-02T16:04:40.092Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ae/29b87775fadc43e627cf582fe3bda4d02e300f6b8f2747c764950d13784c/sqlalchemy-2.0.48-cp313-cp313t-win32.whl", hash = "sha256:9764014ef5e58aab76220c5664abb5d47d5bc858d9debf821e55cfdd0f128485", size = 2141335, upload-time = "2026-03-02T15:52:51.518Z" }, + { url = "https://files.pythonhosted.org/packages/91/44/f39d063c90f2443e5b46ec4819abd3d8de653893aae92df42a5c4f5843de/sqlalchemy-2.0.48-cp313-cp313t-win_amd64.whl", hash = "sha256:e2f35b4cccd9ed286ad62e0a3c3ac21e06c02abc60e20aa51a3e305a30f5fa79", size = 2173095, upload-time = "2026-03-02T15:52:52.79Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b3/f437eaa1cf028bb3c927172c7272366393e73ccd104dcf5b6963f4ab5318/sqlalchemy-2.0.48-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e2d0d88686e3d35a76f3e15a34e8c12d73fc94c1dea1cd55782e695cc14086dd", size = 2154401, upload-time = "2026-03-02T15:49:17.24Z" }, + { url = "https://files.pythonhosted.org/packages/6c/1c/b3abdf0f402aa3f60f0df6ea53d92a162b458fca2321d8f1f00278506402/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49b7bddc1eebf011ea5ab722fdbe67a401caa34a350d278cc7733c0e88fecb1f", size = 3274528, upload-time = "2026-03-02T15:50:41.489Z" }, + { url = "https://files.pythonhosted.org/packages/f2/5e/327428a034407651a048f5e624361adf3f9fbac9d0fa98e981e9c6ff2f5e/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:426c5ca86415d9b8945c7073597e10de9644802e2ff502b8e1f11a7a2642856b", size = 3279523, upload-time = "2026-03-02T15:53:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ca/ece73c81a918add0965b76b868b7b5359e068380b90ef1656ee995940c02/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:288937433bd44e3990e7da2402fabc44a3c6c25d3704da066b85b89a85474ae0", size = 3224312, upload-time = "2026-03-02T15:50:42.996Z" }, + { url = "https://files.pythonhosted.org/packages/88/11/fbaf1ae91fa4ee43f4fe79661cead6358644824419c26adb004941bdce7c/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8183dc57ae7d9edc1346e007e840a9f3d6aa7b7f165203a99e16f447150140d2", size = 3246304, upload-time = "2026-03-02T15:53:34.937Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5fb0deb13930b4f2f698c5541ae076c18981173e27dd00376dbaea7a9c82/sqlalchemy-2.0.48-cp314-cp314-win32.whl", hash = "sha256:1182437cb2d97988cfea04cf6cdc0b0bb9c74f4d56ec3d08b81e23d621a28cc6", size = 2116565, upload-time = "2026-03-02T15:54:38.321Z" }, + { url = "https://files.pythonhosted.org/packages/95/7e/e83615cb63f80047f18e61e31e8e32257d39458426c23006deeaf48f463b/sqlalchemy-2.0.48-cp314-cp314-win_amd64.whl", hash = "sha256:144921da96c08feb9e2b052c5c5c1d0d151a292c6135623c6b2c041f2a45f9e0", size = 2142205, upload-time = "2026-03-02T15:54:39.831Z" }, + { url = "https://files.pythonhosted.org/packages/83/e3/69d8711b3f2c5135e9cde5f063bc1605860f0b2c53086d40c04017eb1f77/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5aee45fd2c6c0f2b9cdddf48c48535e7471e42d6fb81adfde801da0bd5b93241", size = 3563519, upload-time = "2026-03-02T15:57:52.387Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4f/a7cce98facca73c149ea4578981594aaa5fd841e956834931de503359336/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cddca31edf8b0653090cbb54562ca027c421c58ddde2c0685f49ff56a1690e0", size = 3528611, upload-time = "2026-03-02T16:04:42.097Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7d/5936c7a03a0b0cb0fa0cc425998821c6029756b0855a8f7ee70fba1de955/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7a936f1bb23d370b7c8cc079d5fce4c7d18da87a33c6744e51a93b0f9e97e9b3", size = 3472326, upload-time = "2026-03-02T15:57:54.423Z" }, + { url = "https://files.pythonhosted.org/packages/f4/33/cea7dfc31b52904efe3dcdc169eb4514078887dff1f5ae28a7f4c5d54b3c/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e004aa9248e8cb0a5f9b96d003ca7c1c0a5da8decd1066e7b53f59eb8ce7c62b", size = 3478453, upload-time = "2026-03-02T16:04:44.584Z" }, + { url = "https://files.pythonhosted.org/packages/c8/95/32107c4d13be077a9cae61e9ae49966a35dc4bf442a8852dd871db31f62e/sqlalchemy-2.0.48-cp314-cp314t-win32.whl", hash = "sha256:b8438ec5594980d405251451c5b7ea9aa58dda38eb7ac35fb7e4c696712ee24f", size = 2147209, upload-time = "2026-03-02T15:52:54.274Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d7/1e073da7a4bc645eb83c76067284a0374e643bc4be57f14cc6414656f92c/sqlalchemy-2.0.48-cp314-cp314t-win_amd64.whl", hash = "sha256:d854b3970067297f3a7fbd7a4683587134aa9b3877ee15aa29eea478dc68f933", size = 2182198, upload-time = "2026-03-02T15:52:55.606Z" }, + { url = "https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl", hash = "sha256:a66fe406437dd65cacd96a72689a3aaaecaebbcd62d81c5ac1c0fdbeac835096", size = 1940202, upload-time = "2026-03-02T15:52:43.285Z" }, ] [package.optional-dependencies] @@ -1558,72 +1574,86 @@ wheels = [ [[package]] name = "wrapt" -version = "2.1.1" +version = "2.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f7/37/ae31f40bec90de2f88d9597d0b5281e23ffe85b893a47ca5d9c05c63a4f6/wrapt-2.1.1.tar.gz", hash = "sha256:5fdcb09bf6db023d88f312bd0767594b414655d58090fc1c46b3414415f67fac", size = 81329, upload-time = "2026-02-03T02:12:13.786Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/64/925f213fdcbb9baeb1530449ac71a4d57fc361c053d06bf78d0c5c7cd80c/wrapt-2.1.2.tar.gz", hash = "sha256:3996a67eecc2c68fd47b4e3c564405a5777367adfd9b8abb58387b63ee83b21e", size = 81678, upload-time = "2026-03-06T02:53:25.134Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/21/293b657a27accfbbbb6007ebd78af0efa2083dac83e8f523272ea09b4638/wrapt-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7e927375e43fd5a985b27a8992327c22541b6dede1362fc79df337d26e23604f", size = 60554, upload-time = "2026-02-03T02:11:17.362Z" }, - { url = "https://files.pythonhosted.org/packages/25/e9/96dd77728b54a899d4ce2798d7b1296989ce687ed3c0cb917d6b3154bf5d/wrapt-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c99544b6a7d40ca22195563b6d8bc3986ee8bb82f272f31f0670fe9440c869", size = 61496, upload-time = "2026-02-03T02:12:54.732Z" }, - { url = "https://files.pythonhosted.org/packages/44/79/4c755b45df6ef30c0dd628ecfaa0c808854be147ca438429da70a162833c/wrapt-2.1.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b2be3fa5f4efaf16ee7c77d0556abca35f5a18ad4ac06f0ef3904c3399010ce9", size = 113528, upload-time = "2026-02-03T02:12:26.405Z" }, - { url = "https://files.pythonhosted.org/packages/9f/63/23ce28f7b841217d9a6337a340fbb8d4a7fbd67a89d47f377c8550fa34aa/wrapt-2.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67c90c1ae6489a6cb1a82058902caa8006706f7b4e8ff766f943e9d2c8e608d0", size = 115536, upload-time = "2026-02-03T02:11:54.397Z" }, - { url = "https://files.pythonhosted.org/packages/23/7b/5ca8d3b12768670d16c8329e29960eedd56212770365a02a8de8bf73dc01/wrapt-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05c0db35ccffd7480143e62df1e829d101c7b86944ae3be7e4869a7efa621f53", size = 114716, upload-time = "2026-02-03T02:12:20.771Z" }, - { url = "https://files.pythonhosted.org/packages/c7/3a/9789ccb14a096d30bb847bf3ee137bf682cc9750c2ce155f4c5ae1962abf/wrapt-2.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0c2ec9f616755b2e1e0bf4d0961f59bb5c2e7a77407e7e2c38ef4f7d2fdde12c", size = 113200, upload-time = "2026-02-03T02:12:07.688Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e5/4ec3526ce6ce920b267c8d35d2c2f0874d3fad2744c8b7259353f1132baa/wrapt-2.1.1-cp310-cp310-win32.whl", hash = "sha256:203ba6b3f89e410e27dbd30ff7dccaf54dcf30fda0b22aa1b82d560c7f9fe9a1", size = 57876, upload-time = "2026-02-03T02:11:42.61Z" }, - { url = "https://files.pythonhosted.org/packages/d1/4e/661c7c76ecd85375b2bc03488941a3a1078642af481db24949e2b9de01f4/wrapt-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:6f9426d9cfc2f8732922fc96198052e55c09bb9db3ddaa4323a18e055807410e", size = 60224, upload-time = "2026-02-03T02:11:19.096Z" }, - { url = "https://files.pythonhosted.org/packages/5f/b7/53c7252d371efada4cb119e72e774fa2c6b3011fc33e3e552cdf48fb9488/wrapt-2.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:69c26f51b67076b40714cff81bdd5826c0b10c077fb6b0678393a6a2f952a5fc", size = 58645, upload-time = "2026-02-03T02:12:10.396Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a8/9254e4da74b30a105935197015b18b31b7a298bf046e67d8952ef74967bd/wrapt-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c366434a7fb914c7a5de508ed735ef9c133367114e1a7cb91dfb5cd806a1549", size = 60554, upload-time = "2026-02-03T02:11:13.038Z" }, - { url = "https://files.pythonhosted.org/packages/9e/a1/378579880cc7af226354054a2c255f69615b379d8adad482bfe2f22a0dc2/wrapt-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d6a2068bd2e1e19e5a317c8c0b288267eec4e7347c36bc68a6e378a39f19ee7", size = 61491, upload-time = "2026-02-03T02:12:56.077Z" }, - { url = "https://files.pythonhosted.org/packages/dc/72/957b51c56acca35701665878ad31626182199fc4afecfe67dea072210f95/wrapt-2.1.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:891ab4713419217b2aed7dd106c9200f64e6a82226775a0d2ebd6bef2ebd1747", size = 113949, upload-time = "2026-02-03T02:11:04.516Z" }, - { url = "https://files.pythonhosted.org/packages/cd/74/36bbebb4a3d2ae9c3e6929639721f8606cd0710a82a777c371aa69e36504/wrapt-2.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8ef36a0df38d2dc9d907f6617f89e113c5892e0a35f58f45f75901af0ce7d81", size = 115989, upload-time = "2026-02-03T02:12:19.398Z" }, - { url = "https://files.pythonhosted.org/packages/ae/0d/f1177245a083c7be284bc90bddfe5aece32cdd5b858049cb69ce001a0e8d/wrapt-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76e9af3ebd86f19973143d4d592cbf3e970cf3f66ddee30b16278c26ae34b8ab", size = 115242, upload-time = "2026-02-03T02:11:08.111Z" }, - { url = "https://files.pythonhosted.org/packages/62/3e/3b7cf5da27e59df61b1eae2d07dd03ff5d6f75b5408d694873cca7a8e33c/wrapt-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ff562067485ebdeaef2fa3fe9b1876bc4e7b73762e0a01406ad81e2076edcebf", size = 113676, upload-time = "2026-02-03T02:12:41.026Z" }, - { url = "https://files.pythonhosted.org/packages/f7/65/8248d3912c705f2c66f81cb97c77436f37abcbedb16d633b5ab0d795d8cd/wrapt-2.1.1-cp311-cp311-win32.whl", hash = "sha256:9e60a30aa0909435ec4ea2a3c53e8e1b50ac9f640c0e9fe3f21fd248a22f06c5", size = 57863, upload-time = "2026-02-03T02:12:18.112Z" }, - { url = "https://files.pythonhosted.org/packages/6b/31/d29310ab335f71f00c50466153b3dc985aaf4a9fc03263e543e136859541/wrapt-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:7d79954f51fcf84e5ec4878ab4aea32610d70145c5bbc84b3370eabfb1e096c2", size = 60224, upload-time = "2026-02-03T02:12:29.289Z" }, - { url = "https://files.pythonhosted.org/packages/0c/90/a6ec319affa6e2894962a0cb9d73c67f88af1a726d15314bfb5c88b8a08d/wrapt-2.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:d3ffc6b0efe79e08fd947605fd598515aebefe45e50432dc3b5cd437df8b1ada", size = 58643, upload-time = "2026-02-03T02:12:43.022Z" }, - { url = "https://files.pythonhosted.org/packages/df/cb/4d5255d19bbd12be7f8ee2c1fb4269dddec9cef777ef17174d357468efaa/wrapt-2.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab8e3793b239db021a18782a5823fcdea63b9fe75d0e340957f5828ef55fcc02", size = 61143, upload-time = "2026-02-03T02:11:46.313Z" }, - { url = "https://files.pythonhosted.org/packages/6f/07/7ed02daa35542023464e3c8b7cb937fa61f6c61c0361ecf8f5fecf8ad8da/wrapt-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7c0300007836373d1c2df105b40777986accb738053a92fe09b615a7a4547e9f", size = 61740, upload-time = "2026-02-03T02:12:51.966Z" }, - { url = "https://files.pythonhosted.org/packages/c4/60/a237a4e4a36f6d966061ccc9b017627d448161b19e0a3ab80a7c7c97f859/wrapt-2.1.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2b27c070fd1132ab23957bcd4ee3ba707a91e653a9268dc1afbd39b77b2799f7", size = 121327, upload-time = "2026-02-03T02:11:06.796Z" }, - { url = "https://files.pythonhosted.org/packages/ae/fe/9139058a3daa8818fc67e6460a2340e8bbcf3aef8b15d0301338bbe181ca/wrapt-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b0e36d845e8b6f50949b6b65fc6cd279f47a1944582ed4ec8258cd136d89a64", size = 122903, upload-time = "2026-02-03T02:12:48.657Z" }, - { url = "https://files.pythonhosted.org/packages/91/10/b8479202b4164649675846a531763531f0a6608339558b5a0a718fc49a8d/wrapt-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4aeea04a9889370fcfb1ef828c4cc583f36a875061505cd6cd9ba24d8b43cc36", size = 121333, upload-time = "2026-02-03T02:11:32.148Z" }, - { url = "https://files.pythonhosted.org/packages/5f/75/75fc793b791d79444aca2c03ccde64e8b99eda321b003f267d570b7b0985/wrapt-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d88b46bb0dce9f74b6817bc1758ff2125e1ca9e1377d62ea35b6896142ab6825", size = 120458, upload-time = "2026-02-03T02:11:16.039Z" }, - { url = "https://files.pythonhosted.org/packages/d7/8f/c3f30d511082ca6d947c405f9d8f6c8eaf83cfde527c439ec2c9a30eb5ea/wrapt-2.1.1-cp312-cp312-win32.whl", hash = "sha256:63decff76ca685b5c557082dfbea865f3f5f6d45766a89bff8dc61d336348833", size = 58086, upload-time = "2026-02-03T02:12:35.041Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c8/37625b643eea2849f10c3b90f69c7462faa4134448d4443234adaf122ae5/wrapt-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:b828235d26c1e35aca4107039802ae4b1411be0fe0367dd5b7e4d90e562fcbcd", size = 60328, upload-time = "2026-02-03T02:12:45.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/79/56242f07572d5682ba8065a9d4d9c2218313f576e3c3471873c2a5355ffd/wrapt-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:75128507413a9f1bcbe2db88fd18fbdbf80f264b82fa33a6996cdeaf01c52352", size = 58722, upload-time = "2026-02-03T02:12:27.949Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ca/3cf290212855b19af9fcc41b725b5620b32f470d6aad970c2593500817eb/wrapt-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ce9646e17fa7c3e2e7a87e696c7de66512c2b4f789a8db95c613588985a2e139", size = 61150, upload-time = "2026-02-03T02:12:50.575Z" }, - { url = "https://files.pythonhosted.org/packages/9d/33/5b8f89a82a9859ce82da4870c799ad11ce15648b6e1c820fec3e23f4a19f/wrapt-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:428cfc801925454395aa468ba7ddb3ed63dc0d881df7b81626cdd433b4e2b11b", size = 61743, upload-time = "2026-02-03T02:11:55.733Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2f/60c51304fbdf47ce992d9eefa61fbd2c0e64feee60aaa439baf42ea6f40b/wrapt-2.1.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5797f65e4d58065a49088c3b32af5410751cd485e83ba89e5a45e2aa8905af98", size = 121341, upload-time = "2026-02-03T02:11:20.461Z" }, - { url = "https://files.pythonhosted.org/packages/ad/03/ce5256e66dd94e521ad5e753c78185c01b6eddbed3147be541f4d38c0cb7/wrapt-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a2db44a71202c5ae4bb5f27c6d3afbc5b23053f2e7e78aa29704541b5dad789", size = 122947, upload-time = "2026-02-03T02:11:33.596Z" }, - { url = "https://files.pythonhosted.org/packages/eb/ae/50ca8854b81b946a11a36fcd6ead32336e6db2c14b6e4a8b092b80741178/wrapt-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8d5350c3590af09c1703dd60ec78a7370c0186e11eaafb9dda025a30eee6492d", size = 121370, upload-time = "2026-02-03T02:11:09.886Z" }, - { url = "https://files.pythonhosted.org/packages/fb/d9/d6a7c654e0043319b4cc137a4caaf7aa16b46b51ee8df98d1060254705b7/wrapt-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d9b076411bed964e752c01b49fd224cc385f3a96f520c797d38412d70d08359", size = 120465, upload-time = "2026-02-03T02:11:37.592Z" }, - { url = "https://files.pythonhosted.org/packages/55/90/65be41e40845d951f714b5a77e84f377a3787b1e8eee6555a680da6d0db5/wrapt-2.1.1-cp313-cp313-win32.whl", hash = "sha256:0bb7207130ce6486727baa85373503bf3334cc28016f6928a0fa7e19d7ecdc06", size = 58090, upload-time = "2026-02-03T02:12:53.342Z" }, - { url = "https://files.pythonhosted.org/packages/5f/66/6a09e0294c4fc8c26028a03a15191721c9271672467cc33e6617ee0d91d2/wrapt-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:cbfee35c711046b15147b0ae7db9b976f01c9520e6636d992cd9e69e5e2b03b1", size = 60341, upload-time = "2026-02-03T02:12:36.384Z" }, - { url = "https://files.pythonhosted.org/packages/7a/f0/20ceb8b701e9a71555c87a5ddecbed76ec16742cf1e4b87bbaf26735f998/wrapt-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:7d2756061022aebbf57ba14af9c16e8044e055c22d38de7bf40d92b565ecd2b0", size = 58731, upload-time = "2026-02-03T02:12:01.328Z" }, - { url = "https://files.pythonhosted.org/packages/80/b4/fe95beb8946700b3db371f6ce25115217e7075ca063663b8cca2888ba55c/wrapt-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4814a3e58bc6971e46baa910ecee69699110a2bf06c201e24277c65115a20c20", size = 62969, upload-time = "2026-02-03T02:11:51.245Z" }, - { url = "https://files.pythonhosted.org/packages/b8/89/477b0bdc784e3299edf69c279697372b8bd4c31d9c6966eae405442899df/wrapt-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:106c5123232ab9b9f4903692e1fa0bdc231510098f04c13c3081f8ad71c3d612", size = 63606, upload-time = "2026-02-03T02:12:02.64Z" }, - { url = "https://files.pythonhosted.org/packages/ed/55/9d0c1269ab76de87715b3b905df54dd25d55bbffd0b98696893eb613469f/wrapt-2.1.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1a40b83ff2535e6e56f190aff123821eea89a24c589f7af33413b9c19eb2c738", size = 152536, upload-time = "2026-02-03T02:11:24.492Z" }, - { url = "https://files.pythonhosted.org/packages/44/18/2004766030462f79ad86efaa62000b5e39b1ff001dcce86650e1625f40ae/wrapt-2.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:789cea26e740d71cf1882e3a42bb29052bc4ada15770c90072cb47bf73fb3dbf", size = 158697, upload-time = "2026-02-03T02:12:32.214Z" }, - { url = "https://files.pythonhosted.org/packages/e1/bb/0a880fa0f35e94ee843df4ee4dd52a699c9263f36881311cfb412c09c3e5/wrapt-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ba49c14222d5e5c0ee394495a8655e991dc06cbca5398153aefa5ac08cd6ccd7", size = 155563, upload-time = "2026-02-03T02:11:49.737Z" }, - { url = "https://files.pythonhosted.org/packages/42/ff/cd1b7c4846c8678fac359a6eb975dc7ab5bd606030adb22acc8b4a9f53f1/wrapt-2.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ac8cda531fe55be838a17c62c806824472bb962b3afa47ecbd59b27b78496f4e", size = 150161, upload-time = "2026-02-03T02:12:33.613Z" }, - { url = "https://files.pythonhosted.org/packages/38/ec/67c90a7082f452964b4621e4890e9a490f1add23cdeb7483cc1706743291/wrapt-2.1.1-cp313-cp313t-win32.whl", hash = "sha256:b8af75fe20d381dd5bcc9db2e86a86d7fcfbf615383a7147b85da97c1182225b", size = 59783, upload-time = "2026-02-03T02:11:39.863Z" }, - { url = "https://files.pythonhosted.org/packages/ec/08/466afe4855847d8febdfa2c57c87e991fc5820afbdef01a273683dfd15a0/wrapt-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:45c5631c9b6c792b78be2d7352129f776dd72c605be2c3a4e9be346be8376d83", size = 63082, upload-time = "2026-02-03T02:12:09.075Z" }, - { url = "https://files.pythonhosted.org/packages/9a/62/60b629463c28b15b1eeadb3a0691e17568622b12aa5bfa7ebe9b514bfbeb/wrapt-2.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:da815b9263947ac98d088b6414ac83507809a1d385e4632d9489867228d6d81c", size = 60251, upload-time = "2026-02-03T02:11:21.794Z" }, - { url = "https://files.pythonhosted.org/packages/95/a0/1c2396e272f91efe6b16a6a8bce7ad53856c8f9ae4f34ceaa711d63ec9e1/wrapt-2.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9aa1765054245bb01a37f615503290d4e207e3fd59226e78341afb587e9c1236", size = 61311, upload-time = "2026-02-03T02:12:44.41Z" }, - { url = "https://files.pythonhosted.org/packages/b0/9a/d2faba7e61072a7507b5722db63562fdb22f5a24e237d460d18755627f15/wrapt-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:feff14b63a6d86c1eee33a57f77573649f2550935981625be7ff3cb7342efe05", size = 61805, upload-time = "2026-02-03T02:11:59.905Z" }, - { url = "https://files.pythonhosted.org/packages/db/56/073989deb4b5d7d6e7ea424476a4ae4bda02140f2dbeaafb14ba4864dd60/wrapt-2.1.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81fc5f22d5fcfdbabde96bb3f5379b9f4476d05c6d524d7259dc5dfb501d3281", size = 120308, upload-time = "2026-02-03T02:12:04.46Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b6/84f37261295e38167a29eb82affaf1dc15948dc416925fe2091beee8e4ac/wrapt-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:951b228ecf66def855d22e006ab9a1fc12535111ae7db2ec576c728f8ddb39e8", size = 122688, upload-time = "2026-02-03T02:11:23.148Z" }, - { url = "https://files.pythonhosted.org/packages/ea/80/32db2eec6671f80c65b7ff175be61bc73d7f5223f6910b0c921bbc4bd11c/wrapt-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ddf582a95641b9a8c8bd643e83f34ecbbfe1b68bc3850093605e469ab680ae3", size = 121115, upload-time = "2026-02-03T02:12:39.068Z" }, - { url = "https://files.pythonhosted.org/packages/49/ef/dcd00383df0cd696614127902153bf067971a5aabcd3c9dcb2d8ef354b2a/wrapt-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fc5c500966bf48913f795f1984704e6d452ba2414207b15e1f8c339a059d5b16", size = 119484, upload-time = "2026-02-03T02:11:48.419Z" }, - { url = "https://files.pythonhosted.org/packages/76/29/0630280cdd2bd8f86f35cb6854abee1c9d6d1a28a0c6b6417cd15d378325/wrapt-2.1.1-cp314-cp314-win32.whl", hash = "sha256:4aa4baadb1f94b71151b8e44a0c044f6af37396c3b8bcd474b78b49e2130a23b", size = 58514, upload-time = "2026-02-03T02:11:58.616Z" }, - { url = "https://files.pythonhosted.org/packages/db/19/5bed84f9089ed2065f6aeda5dfc4f043743f642bc871454b261c3d7d322b/wrapt-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:860e9d3fd81816a9f4e40812f28be4439ab01f260603c749d14be3c0a1170d19", size = 60763, upload-time = "2026-02-03T02:12:24.553Z" }, - { url = "https://files.pythonhosted.org/packages/e4/cb/b967f2f9669e4249b4fe82e630d2a01bc6b9e362b9b12ed91bbe23ae8df4/wrapt-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:3c59e103017a2c1ea0ddf589cbefd63f91081d7ce9d491d69ff2512bb1157e23", size = 59051, upload-time = "2026-02-03T02:11:29.602Z" }, - { url = "https://files.pythonhosted.org/packages/eb/19/6fed62be29f97eb8a56aff236c3f960a4b4a86e8379dc7046a8005901a97/wrapt-2.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9fa7c7e1bee9278fc4f5dd8275bc8d25493281a8ec6c61959e37cc46acf02007", size = 63059, upload-time = "2026-02-03T02:12:06.368Z" }, - { url = "https://files.pythonhosted.org/packages/0a/1c/b757fd0adb53d91547ed8fad76ba14a5932d83dde4c994846a2804596378/wrapt-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:39c35e12e8215628984248bd9c8897ce0a474be2a773db207eb93414219d8469", size = 63618, upload-time = "2026-02-03T02:12:23.197Z" }, - { url = "https://files.pythonhosted.org/packages/10/fe/e5ae17b1480957c7988d991b93df9f2425fc51f128cf88144d6a18d0eb12/wrapt-2.1.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:94ded4540cac9125eaa8ddf5f651a7ec0da6f5b9f248fe0347b597098f8ec14c", size = 152544, upload-time = "2026-02-03T02:11:43.915Z" }, - { url = "https://files.pythonhosted.org/packages/3e/cc/99aed210c6b547b8a6e4cb9d1425e4466727158a6aeb833aa7997e9e08dd/wrapt-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da0af328373f97ed9bdfea24549ac1b944096a5a71b30e41c9b8b53ab3eec04a", size = 158700, upload-time = "2026-02-03T02:12:30.684Z" }, - { url = "https://files.pythonhosted.org/packages/81/0e/d442f745f4957944d5f8ad38bc3a96620bfff3562533b87e486e979f3d99/wrapt-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4ad839b55f0bf235f8e337ce060572d7a06592592f600f3a3029168e838469d3", size = 155561, upload-time = "2026-02-03T02:11:28.164Z" }, - { url = "https://files.pythonhosted.org/packages/51/ac/9891816280e0018c48f8dfd61b136af7b0dcb4a088895db2531acde5631b/wrapt-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0d89c49356e5e2a50fa86b40e0510082abcd0530f926cbd71cf25bee6b9d82d7", size = 150188, upload-time = "2026-02-03T02:11:57.053Z" }, - { url = "https://files.pythonhosted.org/packages/24/98/e2f273b6d70d41f98d0739aa9a269d0b633684a5fb17b9229709375748d4/wrapt-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:f4c7dd22cf7f36aafe772f3d88656559205c3af1b7900adfccb70edeb0d2abc4", size = 60425, upload-time = "2026-02-03T02:11:35.007Z" }, - { url = "https://files.pythonhosted.org/packages/1e/06/b500bfc38a4f82d89f34a13069e748c82c5430d365d9e6b75afb3ab74457/wrapt-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f76bc12c583ab01e73ba0ea585465a41e48d968f6d1311b4daec4f8654e356e3", size = 63855, upload-time = "2026-02-03T02:12:15.47Z" }, - { url = "https://files.pythonhosted.org/packages/d9/cc/5f6193c32166faee1d2a613f278608e6f3b95b96589d020f0088459c46c9/wrapt-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7ea74fc0bec172f1ae5f3505b6655c541786a5cabe4bbc0d9723a56ac32eb9b9", size = 60443, upload-time = "2026-02-03T02:11:30.869Z" }, - { url = "https://files.pythonhosted.org/packages/c4/da/5a086bf4c22a41995312db104ec2ffeee2cf6accca9faaee5315c790377d/wrapt-2.1.1-py3-none-any.whl", hash = "sha256:3b0f4629eb954394a3d7c7a1c8cca25f0b07cefe6aa8545e862e9778152de5b7", size = 43886, upload-time = "2026-02-03T02:11:45.048Z" }, + { url = "https://files.pythonhosted.org/packages/da/d2/387594fb592d027366645f3d7cc9b4d7ca7be93845fbaba6d835a912ef3c/wrapt-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a86d99a14f76facb269dc148590c01aaf47584071809a70da30555228158c", size = 60669, upload-time = "2026-03-06T02:52:40.671Z" }, + { url = "https://files.pythonhosted.org/packages/c9/18/3f373935bc5509e7ac444c8026a56762e50c1183e7061797437ca96c12ce/wrapt-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a819e39017f95bf7aede768f75915635aa8f671f2993c036991b8d3bfe8dbb6f", size = 61603, upload-time = "2026-03-06T02:54:21.032Z" }, + { url = "https://files.pythonhosted.org/packages/c2/7a/32758ca2853b07a887a4574b74e28843919103194bb47001a304e24af62f/wrapt-2.1.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5681123e60aed0e64c7d44f72bbf8b4ce45f79d81467e2c4c728629f5baf06eb", size = 113632, upload-time = "2026-03-06T02:53:54.121Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d5/eeaa38f670d462e97d978b3b0d9ce06d5b91e54bebac6fbed867809216e7/wrapt-2.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b8b28e97a44d21836259739ae76284e180b18abbb4dcfdff07a415cf1016c3e", size = 115644, upload-time = "2026-03-06T02:54:53.33Z" }, + { url = "https://files.pythonhosted.org/packages/e3/09/2a41506cb17affb0bdf9d5e2129c8c19e192b388c4c01d05e1b14db23c00/wrapt-2.1.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cef91c95a50596fcdc31397eb6955476f82ae8a3f5a8eabdc13611b60ee380ba", size = 112016, upload-time = "2026-03-06T02:54:43.274Z" }, + { url = "https://files.pythonhosted.org/packages/64/15/0e6c3f5e87caadc43db279724ee36979246d5194fa32fed489c73643ba59/wrapt-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dad63212b168de8569b1c512f4eac4b57f2c6934b30df32d6ee9534a79f1493f", size = 114823, upload-time = "2026-03-06T02:54:29.392Z" }, + { url = "https://files.pythonhosted.org/packages/56/b2/0ad17c8248f4e57bedf44938c26ec3ee194715f812d2dbbd9d7ff4be6c06/wrapt-2.1.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d307aa6888d5efab2c1cde09843d48c843990be13069003184b67d426d145394", size = 111244, upload-time = "2026-03-06T02:54:02.149Z" }, + { url = "https://files.pythonhosted.org/packages/ff/04/bcdba98c26f2c6522c7c09a726d5d9229120163493620205b2f76bd13c01/wrapt-2.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c87cf3f0c85e27b3ac7d9ad95da166bf8739ca215a8b171e8404a2d739897a45", size = 113307, upload-time = "2026-03-06T02:54:12.428Z" }, + { url = "https://files.pythonhosted.org/packages/0e/1b/5e2883c6bc14143924e465a6fc5a92d09eeabe35310842a481fb0581f832/wrapt-2.1.2-cp310-cp310-win32.whl", hash = "sha256:d1c5fea4f9fe3762e2b905fdd67df51e4be7a73b7674957af2d2ade71a5c075d", size = 57986, upload-time = "2026-03-06T02:54:26.823Z" }, + { url = "https://files.pythonhosted.org/packages/42/5a/4efc997bccadd3af5749c250b49412793bc41e13a83a486b2b54a33e240c/wrapt-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:d8f7740e1af13dff2684e4d56fe604a7e04d6c94e737a60568d8d4238b9a0c71", size = 60336, upload-time = "2026-03-06T02:54:18Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f5/a2bb833e20181b937e87c242645ed5d5aa9c373006b0467bfe1a35c727d0/wrapt-2.1.2-cp310-cp310-win_arm64.whl", hash = "sha256:1c6cc827c00dc839350155f316f1f8b4b0c370f52b6a19e782e2bda89600c7dc", size = 58757, upload-time = "2026-03-06T02:53:51.545Z" }, + { url = "https://files.pythonhosted.org/packages/c7/81/60c4471fce95afa5922ca09b88a25f03c93343f759aae0f31fb4412a85c7/wrapt-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96159a0ee2b0277d44201c3b5be479a9979cf154e8c82fa5df49586a8e7679bb", size = 60666, upload-time = "2026-03-06T02:52:58.934Z" }, + { url = "https://files.pythonhosted.org/packages/6b/be/80e80e39e7cb90b006a0eaf11c73ac3a62bbfb3068469aec15cc0bc795de/wrapt-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98ba61833a77b747901e9012072f038795de7fc77849f1faa965464f3f87ff2d", size = 61601, upload-time = "2026-03-06T02:53:00.487Z" }, + { url = "https://files.pythonhosted.org/packages/b0/be/d7c88cd9293c859fc74b232abdc65a229bb953997995d6912fc85af18323/wrapt-2.1.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:767c0dbbe76cae2a60dd2b235ac0c87c9cccf4898aef8062e57bead46b5f6894", size = 114057, upload-time = "2026-03-06T02:52:44.08Z" }, + { url = "https://files.pythonhosted.org/packages/ea/25/36c04602831a4d685d45a93b3abea61eca7fe35dab6c842d6f5d570ef94a/wrapt-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c691a6bc752c0cc4711cc0c00896fcd0f116abc253609ef64ef930032821842", size = 116099, upload-time = "2026-03-06T02:54:56.74Z" }, + { url = "https://files.pythonhosted.org/packages/5c/4e/98a6eb417ef551dc277bec1253d5246b25003cf36fdf3913b65cb7657a56/wrapt-2.1.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f3b7d73012ea75aee5844de58c88f44cf62d0d62711e39da5a82824a7c4626a8", size = 112457, upload-time = "2026-03-06T02:53:52.842Z" }, + { url = "https://files.pythonhosted.org/packages/cb/a6/a6f7186a5297cad8ec53fd7578533b28f795fdf5372368c74bd7e6e9841c/wrapt-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:577dff354e7acd9d411eaf4bfe76b724c89c89c8fc9b7e127ee28c5f7bcb25b6", size = 115351, upload-time = "2026-03-06T02:53:32.684Z" }, + { url = "https://files.pythonhosted.org/packages/97/6f/06e66189e721dbebd5cf20e138acc4d1150288ce118462f2fcbff92d38db/wrapt-2.1.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d7b6fd105f8b24e5bd23ccf41cb1d1099796524bcc6f7fbb8fe576c44befbc9", size = 111748, upload-time = "2026-03-06T02:53:08.455Z" }, + { url = "https://files.pythonhosted.org/packages/ef/43/4808b86f499a51370fbdbdfa6cb91e9b9169e762716456471b619fca7a70/wrapt-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:866abdbf4612e0b34764922ef8b1c5668867610a718d3053d59e24a5e5fcfc15", size = 113783, upload-time = "2026-03-06T02:53:02.02Z" }, + { url = "https://files.pythonhosted.org/packages/91/2c/a3f28b8fa7ac2cefa01cfcaca3471f9b0460608d012b693998cd61ef43df/wrapt-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5a0a0a3a882393095573344075189eb2d566e0fd205a2b6414e9997b1b800a8b", size = 57977, upload-time = "2026-03-06T02:53:27.844Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c3/2b1c7bd07a27b1db885a2fab469b707bdd35bddf30a113b4917a7e2139d2/wrapt-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:64a07a71d2730ba56f11d1a4b91f7817dc79bc134c11516b75d1921a7c6fcda1", size = 60336, upload-time = "2026-03-06T02:54:28.104Z" }, + { url = "https://files.pythonhosted.org/packages/ec/5c/76ece7b401b088daa6503d6264dd80f9a727df3e6042802de9a223084ea2/wrapt-2.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:b89f095fe98bc12107f82a9f7d570dc83a0870291aeb6b1d7a7d35575f55d98a", size = 58756, upload-time = "2026-03-06T02:53:16.319Z" }, + { url = "https://files.pythonhosted.org/packages/4c/b6/1db817582c49c7fcbb7df6809d0f515af29d7c2fbf57eb44c36e98fb1492/wrapt-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ff2aad9c4cda28a8f0653fc2d487596458c2a3f475e56ba02909e950a9efa6a9", size = 61255, upload-time = "2026-03-06T02:52:45.663Z" }, + { url = "https://files.pythonhosted.org/packages/a2/16/9b02a6b99c09227c93cd4b73acc3678114154ec38da53043c0ddc1fba0dc/wrapt-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6433ea84e1cfacf32021d2a4ee909554ade7fd392caa6f7c13f1f4bf7b8e8748", size = 61848, upload-time = "2026-03-06T02:53:48.728Z" }, + { url = "https://files.pythonhosted.org/packages/af/aa/ead46a88f9ec3a432a4832dfedb84092fc35af2d0ba40cd04aea3889f247/wrapt-2.1.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c20b757c268d30d6215916a5fa8461048d023865d888e437fab451139cad6c8e", size = 121433, upload-time = "2026-03-06T02:54:40.328Z" }, + { url = "https://files.pythonhosted.org/packages/3a/9f/742c7c7cdf58b59085a1ee4b6c37b013f66ac33673a7ef4aaed5e992bc33/wrapt-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79847b83eb38e70d93dc392c7c5b587efe65b3e7afcc167aa8abd5d60e8761c8", size = 123013, upload-time = "2026-03-06T02:53:26.58Z" }, + { url = "https://files.pythonhosted.org/packages/e8/44/2c3dd45d53236b7ed7c646fcf212251dc19e48e599debd3926b52310fafb/wrapt-2.1.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f8fba1bae256186a83d1875b2b1f4e2d1242e8fac0f58ec0d7e41b26967b965c", size = 117326, upload-time = "2026-03-06T02:53:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/74/e2/b17d66abc26bd96f89dec0ecd0ef03da4a1286e6ff793839ec431b9fae57/wrapt-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e3d3b35eedcf5f7d022291ecd7533321c4775f7b9cd0050a31a68499ba45757c", size = 121444, upload-time = "2026-03-06T02:54:09.5Z" }, + { url = "https://files.pythonhosted.org/packages/3c/62/e2977843fdf9f03daf1586a0ff49060b1b2fc7ff85a7ea82b6217c1ae36e/wrapt-2.1.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6f2c5390460de57fa9582bc8a1b7a6c86e1a41dfad74c5225fc07044c15cc8d1", size = 116237, upload-time = "2026-03-06T02:54:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/27fc67914e68d740bce512f11734aec08696e6b17641fef8867c00c949fc/wrapt-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7dfa9f2cf65d027b951d05c662cc99ee3bd01f6e4691ed39848a7a5fffc902b2", size = 120563, upload-time = "2026-03-06T02:53:20.412Z" }, + { url = "https://files.pythonhosted.org/packages/ec/9f/b750b3692ed2ef4705cb305bd68858e73010492b80e43d2a4faa5573cbe7/wrapt-2.1.2-cp312-cp312-win32.whl", hash = "sha256:eba8155747eb2cae4a0b913d9ebd12a1db4d860fc4c829d7578c7b989bd3f2f0", size = 58198, upload-time = "2026-03-06T02:53:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/8e/b2/feecfe29f28483d888d76a48f03c4c4d8afea944dbee2b0cd3380f9df032/wrapt-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1c51c738d7d9faa0b3601708e7e2eda9bf779e1b601dce6c77411f2a1b324a63", size = 60441, upload-time = "2026-03-06T02:52:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/44/e1/e328f605d6e208547ea9fd120804fcdec68536ac748987a68c47c606eea8/wrapt-2.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:c8e46ae8e4032792eb2f677dbd0d557170a8e5524d22acc55199f43efedd39bf", size = 58836, upload-time = "2026-03-06T02:53:22.053Z" }, + { url = "https://files.pythonhosted.org/packages/4c/7a/d936840735c828b38d26a854e85d5338894cda544cb7a85a9d5b8b9c4df7/wrapt-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787fd6f4d67befa6fe2abdffcbd3de2d82dfc6fb8a6d850407c53332709d030b", size = 61259, upload-time = "2026-03-06T02:53:41.922Z" }, + { url = "https://files.pythonhosted.org/packages/5e/88/9a9b9a90ac8ca11c2fdb6a286cb3a1fc7dd774c00ed70929a6434f6bc634/wrapt-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4bdf26e03e6d0da3f0e9422fd36bcebf7bc0eeb55fdf9c727a09abc6b9fe472e", size = 61851, upload-time = "2026-03-06T02:52:48.672Z" }, + { url = "https://files.pythonhosted.org/packages/03/a9/5b7d6a16fd6533fed2756900fc8fc923f678179aea62ada6d65c92718c00/wrapt-2.1.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bbac24d879aa22998e87f6b3f481a5216311e7d53c7db87f189a7a0266dafffb", size = 121446, upload-time = "2026-03-06T02:54:14.013Z" }, + { url = "https://files.pythonhosted.org/packages/45/bb/34c443690c847835cfe9f892be78c533d4f32366ad2888972c094a897e39/wrapt-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16997dfb9d67addc2e3f41b62a104341e80cac52f91110dece393923c0ebd5ca", size = 123056, upload-time = "2026-03-06T02:54:10.829Z" }, + { url = "https://files.pythonhosted.org/packages/93/b9/ff205f391cb708f67f41ea148545f2b53ff543a7ac293b30d178af4d2271/wrapt-2.1.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:162e4e2ba7542da9027821cb6e7c5e068d64f9a10b5f15512ea28e954893a267", size = 117359, upload-time = "2026-03-06T02:53:03.623Z" }, + { url = "https://files.pythonhosted.org/packages/1f/3d/1ea04d7747825119c3c9a5e0874a40b33594ada92e5649347c457d982805/wrapt-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f29c827a8d9936ac320746747a016c4bc66ef639f5cd0d32df24f5eacbf9c69f", size = 121479, upload-time = "2026-03-06T02:53:45.844Z" }, + { url = "https://files.pythonhosted.org/packages/78/cc/ee3a011920c7a023b25e8df26f306b2484a531ab84ca5c96260a73de76c0/wrapt-2.1.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a9dd9813825f7ecb018c17fd147a01845eb330254dff86d3b5816f20f4d6aaf8", size = 116271, upload-time = "2026-03-06T02:54:46.356Z" }, + { url = "https://files.pythonhosted.org/packages/98/fd/e5ff7ded41b76d802cf1191288473e850d24ba2e39a6ec540f21ae3b57cb/wrapt-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f8dbdd3719e534860d6a78526aafc220e0241f981367018c2875178cf83a413", size = 120573, upload-time = "2026-03-06T02:52:50.163Z" }, + { url = "https://files.pythonhosted.org/packages/47/c5/242cae3b5b080cd09bacef0591691ba1879739050cc7c801ff35c8886b66/wrapt-2.1.2-cp313-cp313-win32.whl", hash = "sha256:5c35b5d82b16a3bc6e0a04349b606a0582bc29f573786aebe98e0c159bc48db6", size = 58205, upload-time = "2026-03-06T02:53:47.494Z" }, + { url = "https://files.pythonhosted.org/packages/12/69/c358c61e7a50f290958809b3c61ebe8b3838ea3e070d7aac9814f95a0528/wrapt-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f8bc1c264d8d1cf5b3560a87bbdd31131573eb25f9f9447bb6252b8d4c44a3a1", size = 60452, upload-time = "2026-03-06T02:53:30.038Z" }, + { url = "https://files.pythonhosted.org/packages/8e/66/c8a6fcfe321295fd8c0ab1bd685b5a01462a9b3aa2f597254462fc2bc975/wrapt-2.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:3beb22f674550d5634642c645aba4c72a2c66fb185ae1aebe1e955fae5a13baf", size = 58842, upload-time = "2026-03-06T02:52:52.114Z" }, + { url = "https://files.pythonhosted.org/packages/da/55/9c7052c349106e0b3f17ae8db4b23a691a963c334de7f9dbd60f8f74a831/wrapt-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fc04bc8664a8bc4c8e00b37b5355cffca2535209fba1abb09ae2b7c76ddf82b", size = 63075, upload-time = "2026-03-06T02:53:19.108Z" }, + { url = "https://files.pythonhosted.org/packages/09/a8/ce7b4006f7218248dd71b7b2b732d0710845a0e49213b18faef64811ffef/wrapt-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a9b9d50c9af998875a1482a038eb05755dfd6fe303a313f6a940bb53a83c3f18", size = 63719, upload-time = "2026-03-06T02:54:33.452Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e5/2ca472e80b9e2b7a17f106bb8f9df1db11e62101652ce210f66935c6af67/wrapt-2.1.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d3ff4f0024dd224290c0eabf0240f1bfc1f26363431505fb1b0283d3b08f11d", size = 152643, upload-time = "2026-03-06T02:52:42.721Z" }, + { url = "https://files.pythonhosted.org/packages/36/42/30f0f2cefca9d9cbf6835f544d825064570203c3e70aa873d8ae12e23791/wrapt-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3278c471f4468ad544a691b31bb856374fbdefb7fee1a152153e64019379f015", size = 158805, upload-time = "2026-03-06T02:54:25.441Z" }, + { url = "https://files.pythonhosted.org/packages/bb/67/d08672f801f604889dcf58f1a0b424fe3808860ede9e03affc1876b295af/wrapt-2.1.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8914c754d3134a3032601c6984db1c576e6abaf3fc68094bb8ab1379d75ff92", size = 145990, upload-time = "2026-03-06T02:53:57.456Z" }, + { url = "https://files.pythonhosted.org/packages/68/a7/fd371b02e73babec1de6ade596e8cd9691051058cfdadbfd62a5898f3295/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ff95d4264e55839be37bafe1536db2ab2de19da6b65f9244f01f332b5286cfbf", size = 155670, upload-time = "2026-03-06T02:54:55.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/9fe0095dfdb621009f40117dcebf41d7396c2c22dca6eac779f4c007b86c/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:76405518ca4e1b76fbb1b9f686cff93aebae03920cc55ceeec48ff9f719c5f67", size = 144357, upload-time = "2026-03-06T02:54:24.092Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b6/ec7b4a254abbe4cde9fa15c5d2cca4518f6b07d0f1b77d4ee9655e30280e/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c0be8b5a74c5824e9359b53e7e58bef71a729bacc82e16587db1c4ebc91f7c5a", size = 150269, upload-time = "2026-03-06T02:53:31.268Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6b/2fabe8ebf148f4ee3c782aae86a795cc68ffe7d432ef550f234025ce0cfa/wrapt-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:f01277d9a5fc1862f26f7626da9cf443bebc0abd2f303f41c5e995b15887dabd", size = 59894, upload-time = "2026-03-06T02:54:15.391Z" }, + { url = "https://files.pythonhosted.org/packages/ca/fb/9ba66fc2dedc936de5f8073c0217b5d4484e966d87723415cc8262c5d9c2/wrapt-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:84ce8f1c2104d2f6daa912b1b5b039f331febfeee74f8042ad4e04992bd95c8f", size = 63197, upload-time = "2026-03-06T02:54:41.943Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1c/012d7423c95d0e337117723eb8ecf73c622ce15a97847e84cf3f8f26cd7e/wrapt-2.1.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a93cd767e37faeddbe07d8fc4212d5cba660af59bdb0f6372c93faaa13e6e679", size = 60363, upload-time = "2026-03-06T02:54:48.093Z" }, + { url = "https://files.pythonhosted.org/packages/39/25/e7ea0b417db02bb796182a5316398a75792cd9a22528783d868755e1f669/wrapt-2.1.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1370e516598854e5b4366e09ce81e08bfe94d42b0fd569b88ec46cc56d9164a9", size = 61418, upload-time = "2026-03-06T02:53:55.706Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0f/fa539e2f6a770249907757eaeb9a5ff4deb41c026f8466c1c6d799088a9b/wrapt-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6de1a3851c27e0bd6a04ca993ea6f80fc53e6c742ee1601f486c08e9f9b900a9", size = 61914, upload-time = "2026-03-06T02:52:53.37Z" }, + { url = "https://files.pythonhosted.org/packages/53/37/02af1867f5b1441aaeda9c82deed061b7cd1372572ddcd717f6df90b5e93/wrapt-2.1.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:de9f1a2bbc5ac7f6012ec24525bdd444765a2ff64b5985ac6e0692144838542e", size = 120417, upload-time = "2026-03-06T02:54:30.74Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b7/0138a6238c8ba7476c77cf786a807f871672b37f37a422970342308276e7/wrapt-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:970d57ed83fa040d8b20c52fe74a6ae7e3775ae8cff5efd6a81e06b19078484c", size = 122797, upload-time = "2026-03-06T02:54:51.539Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ad/819ae558036d6a15b7ed290d5b14e209ca795dd4da9c58e50c067d5927b0/wrapt-2.1.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3969c56e4563c375861c8df14fa55146e81ac11c8db49ea6fb7f2ba58bc1ff9a", size = 117350, upload-time = "2026-03-06T02:54:37.651Z" }, + { url = "https://files.pythonhosted.org/packages/8b/2d/afc18dc57a4600a6e594f77a9ae09db54f55ba455440a54886694a84c71b/wrapt-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:57d7c0c980abdc5f1d98b11a2aa3bb159790add80258c717fa49a99921456d90", size = 121223, upload-time = "2026-03-06T02:54:35.221Z" }, + { url = "https://files.pythonhosted.org/packages/b9/5b/5ec189b22205697bc56eb3b62aed87a1e0423e9c8285d0781c7a83170d15/wrapt-2.1.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:776867878e83130c7a04237010463372e877c1c994d449ca6aaafeab6aab2586", size = 116287, upload-time = "2026-03-06T02:54:19.654Z" }, + { url = "https://files.pythonhosted.org/packages/f7/2d/f84939a7c9b5e6cdd8a8d0f6a26cabf36a0f7e468b967720e8b0cd2bdf69/wrapt-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fab036efe5464ec3291411fabb80a7a39e2dd80bae9bcbeeca5087fdfa891e19", size = 119593, upload-time = "2026-03-06T02:54:16.697Z" }, + { url = "https://files.pythonhosted.org/packages/0b/fe/ccd22a1263159c4ac811ab9374c061bcb4a702773f6e06e38de5f81a1bdc/wrapt-2.1.2-cp314-cp314-win32.whl", hash = "sha256:e6ed62c82ddf58d001096ae84ce7f833db97ae2263bff31c9b336ba8cfe3f508", size = 58631, upload-time = "2026-03-06T02:53:06.498Z" }, + { url = "https://files.pythonhosted.org/packages/65/0a/6bd83be7bff2e7efaac7b4ac9748da9d75a34634bbbbc8ad077d527146df/wrapt-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:467e7c76315390331c67073073d00662015bb730c566820c9ca9b54e4d67fd04", size = 60875, upload-time = "2026-03-06T02:53:50.252Z" }, + { url = "https://files.pythonhosted.org/packages/6c/c0/0b3056397fe02ff80e5a5d72d627c11eb885d1ca78e71b1a5c1e8c7d45de/wrapt-2.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:da1f00a557c66225d53b095a97eace0fc5349e3bfda28fa34ffae238978ee575", size = 59164, upload-time = "2026-03-06T02:53:59.128Z" }, + { url = "https://files.pythonhosted.org/packages/71/ed/5d89c798741993b2371396eb9d4634f009ff1ad8a6c78d366fe2883ea7a6/wrapt-2.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:62503ffbc2d3a69891cf29beeaccdb4d5e0a126e2b6a851688d4777e01428dbb", size = 63163, upload-time = "2026-03-06T02:52:54.873Z" }, + { url = "https://files.pythonhosted.org/packages/c6/8c/05d277d182bf36b0a13d6bd393ed1dec3468a25b59d01fba2dd70fe4d6ae/wrapt-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7e6cd120ef837d5b6f860a6ea3745f8763805c418bb2f12eeb1fa6e25f22d22", size = 63723, upload-time = "2026-03-06T02:52:56.374Z" }, + { url = "https://files.pythonhosted.org/packages/f4/27/6c51ec1eff4413c57e72d6106bb8dec6f0c7cdba6503d78f0fa98767bcc9/wrapt-2.1.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3769a77df8e756d65fbc050333f423c01ae012b4f6731aaf70cf2bef61b34596", size = 152652, upload-time = "2026-03-06T02:53:23.79Z" }, + { url = "https://files.pythonhosted.org/packages/db/4c/d7dd662d6963fc7335bfe29d512b02b71cdfa23eeca7ab3ac74a67505deb/wrapt-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a76d61a2e851996150ba0f80582dd92a870643fa481f3b3846f229de88caf044", size = 158807, upload-time = "2026-03-06T02:53:35.742Z" }, + { url = "https://files.pythonhosted.org/packages/b4/4d/1e5eea1a78d539d346765727422976676615814029522c76b87a95f6bcdd/wrapt-2.1.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6f97edc9842cf215312b75fe737ee7c8adda75a89979f8e11558dfff6343cc4b", size = 146061, upload-time = "2026-03-06T02:52:57.574Z" }, + { url = "https://files.pythonhosted.org/packages/89/bc/62cabea7695cd12a288023251eeefdcb8465056ddaab6227cb78a2de005b/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4006c351de6d5007aa33a551f600404ba44228a89e833d2fadc5caa5de8edfbf", size = 155667, upload-time = "2026-03-06T02:53:39.422Z" }, + { url = "https://files.pythonhosted.org/packages/e9/99/6f2888cd68588f24df3a76572c69c2de28287acb9e1972bf0c83ce97dbc1/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a9372fc3639a878c8e7d87e1556fa209091b0a66e912c611e3f833e2c4202be2", size = 144392, upload-time = "2026-03-06T02:54:22.41Z" }, + { url = "https://files.pythonhosted.org/packages/40/51/1dfc783a6c57971614c48e361a82ca3b6da9055879952587bc99fe1a7171/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3144b027ff30cbd2fca07c0a87e67011adb717eb5f5bd8496325c17e454257a3", size = 150296, upload-time = "2026-03-06T02:54:07.848Z" }, + { url = "https://files.pythonhosted.org/packages/6c/38/cbb8b933a0201076c1f64fc42883b0023002bdc14a4964219154e6ff3350/wrapt-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:3b8d15e52e195813efe5db8cec156eebe339aaf84222f4f4f051a6c01f237ed7", size = 60539, upload-time = "2026-03-06T02:54:00.594Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/e5176e4b241c9f528402cebb238a36785a628179d7d8b71091154b3e4c9e/wrapt-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:08ffa54146a7559f5b8df4b289b46d963a8e74ed16ba3687f99896101a3990c5", size = 63969, upload-time = "2026-03-06T02:54:39Z" }, + { url = "https://files.pythonhosted.org/packages/5c/99/79f17046cf67e4a95b9987ea129632ba8bcec0bc81f3fb3d19bdb0bd60cd/wrapt-2.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:72aaa9d0d8e4ed0e2e98019cea47a21f823c9dd4b43c7b77bba6679ffcca6a00", size = 60554, upload-time = "2026-03-06T02:53:14.132Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c7/8528ac2dfa2c1e6708f647df7ae144ead13f0a31146f43c7264b4942bf12/wrapt-2.1.2-py3-none-any.whl", hash = "sha256:b8fd6fa2b2c4e7621808f8c62e8317f4aae56e59721ad933bac5239d913cf0e8", size = 43993, upload-time = "2026-03-06T02:53:12.905Z" }, ] From 7ecfb44d9dd178d1ed340f45a0d0813568cce268 Mon Sep 17 00:00:00 2001 From: ddc <34492089+ddc@users.noreply.github.com> Date: Tue, 17 Mar 2026 00:04:32 -0300 Subject: [PATCH 4/5] v4.0.0 --- ddcdatabases/core/base.py | 30 ++++--- ddcdatabases/core/configs.py | 46 +++++++++++ ddcdatabases/core/persistent.py | 79 +++--------------- ddcdatabases/core/settings.py | 140 +++++++------------------------- ddcdatabases/mongodb.py | 41 +++------- ddcdatabases/mssql.py | 109 +++---------------------- ddcdatabases/mysql.py | 86 ++------------------ ddcdatabases/oracle.py | 73 +++-------------- ddcdatabases/postgresql.py | 54 ++---------- ddcdatabases/sqlite.py | 41 +++------- 10 files changed, 170 insertions(+), 529 deletions(-) diff --git a/ddcdatabases/core/base.py b/ddcdatabases/core/base.py index 7fc2be3..fe158d1 100644 --- a/ddcdatabases/core/base.py +++ b/ddcdatabases/core/base.py @@ -3,11 +3,11 @@ import sqlalchemy as sa from .configs import BaseOperationRetryConfig, BaseRetryConfig from .retry import retry_operation, retry_operation_async -from abc import ABC, abstractmethod -from contextlib import AbstractAsyncContextManager, AbstractContextManager +from collections.abc import AsyncGenerator, Generator +from contextlib import asynccontextmanager, contextmanager from datetime import datetime -from sqlalchemy.engine import URL, Engine -from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker +from sqlalchemy.engine import URL, Engine, create_engine +from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.orm import Session, sessionmaker from typing import Any @@ -15,7 +15,7 @@ _logger.addHandler(logging.NullHandler()) -class BaseConnection(ABC): +class BaseConnection: __slots__ = ( "connection_url", "engine_args", @@ -109,13 +109,19 @@ async def __aexit__( self.is_connected = False self.logger.debug("Disconnected") - @abstractmethod - def _get_engine(self) -> AbstractContextManager[Engine]: - pass - - @abstractmethod - def _get_async_engine(self) -> AbstractAsyncContextManager[AsyncEngine]: - pass + @contextmanager + def _get_engine(self) -> Generator[Engine, None, None]: + _connection_url = URL.create(drivername=self.sync_driver, **self.connection_url) + _engine = create_engine(url=_connection_url, **self.engine_args) + yield _engine + _engine.dispose() + + @asynccontextmanager + async def _get_async_engine(self) -> AsyncGenerator[AsyncEngine, None]: + _connection_url = URL.create(drivername=self.async_driver, **self.connection_url) + _engine = create_async_engine(url=_connection_url, **self.engine_args) + yield _engine + await _engine.dispose() def _test_connection_sync(self, session: Session) -> None: _connection_url_copy = self.connection_url.copy() diff --git a/ddcdatabases/core/configs.py b/ddcdatabases/core/configs.py index 8375983..9bbe470 100644 --- a/ddcdatabases/core/configs.py +++ b/ddcdatabases/core/configs.py @@ -1,4 +1,50 @@ +import dataclasses from dataclasses import dataclass +from typing import Any, TypeVar + +_C = TypeVar("_C") + +# Field maps for merging retry configs with settings +CONNECTION_RETRY_FIELD_MAP: dict[str, str] = { + "enable_retry": "connection_enable_retry", + "max_retries": "connection_max_retries", + "initial_retry_delay": "connection_initial_retry_delay", + "max_retry_delay": "connection_max_retry_delay", +} + +OPERATION_RETRY_FIELD_MAP: dict[str, str] = { + "enable_retry": "operation_enable_retry", + "max_retries": "operation_max_retries", + "initial_retry_delay": "operation_initial_retry_delay", + "max_retry_delay": "operation_max_retry_delay", + "jitter": "operation_jitter", +} + + +def merge_config_with_settings( + config_cls: type[_C], + override: _C | None, + settings: Any, + field_map: dict[str, str] | None = None, +) -> _C: + """Create config instance, using override values when not None, else settings defaults. + + Args: + config_cls: The dataclass class to instantiate + override: Optional override instance (or None to use all defaults) + settings: Settings object with default values + field_map: Dict mapping config field names to settings attribute names. + If None, config field names must match settings attribute names. + """ + override = override or config_cls() + field_map = field_map or {} + kwargs = {} + for field in dataclasses.fields(config_cls): # type: ignore[arg-type] + name = field.name + settings_attr = field_map.get(name, name) + val = getattr(override, name) + kwargs[name] = val if val is not None else getattr(settings, settings_attr) + return config_cls(**kwargs) def _validate_retry_config( diff --git a/ddcdatabases/core/persistent.py b/ddcdatabases/core/persistent.py index aafbbe1..bc98905 100644 --- a/ddcdatabases/core/persistent.py +++ b/ddcdatabases/core/persistent.py @@ -13,7 +13,7 @@ import threading import time import weakref -from .configs import BaseOperationRetryConfig, BaseRetryConfig +from .configs import BaseOperationRetryConfig, BaseRetryConfig, merge_config_with_settings from .retry import retry_operation, retry_operation_async from .settings import ( get_mongodb_settings, @@ -49,6 +49,13 @@ class PersistentConnectionConfig: auto_reconnect: bool | None = None +_PERSISTENT_CONFIG_FIELD_MAP: dict[str, str] = { + "idle_timeout": "persistent_idle_timeout", + "health_check_interval": "persistent_health_check_interval", + "auto_reconnect": "persistent_auto_reconnect", +} + + # Global registry for persistent connections (weak references to allow cleanup) _persistent_connections: weakref.WeakValueDictionary[str, BasePersistentConnection | PersistentMongoDBConnection] = ( weakref.WeakValueDictionary() @@ -772,19 +779,7 @@ def __new__( if schema and schema != "public": connection_key += f"?schema={schema}" - # Build config from settings, allowing partial overrides - _cfg = config or PersistentConnectionConfig() - config = PersistentConnectionConfig( - idle_timeout=_cfg.idle_timeout if _cfg.idle_timeout is not None else _settings.persistent_idle_timeout, - health_check_interval=( - _cfg.health_check_interval - if _cfg.health_check_interval is not None - else _settings.persistent_health_check_interval - ), - auto_reconnect=( - _cfg.auto_reconnect if _cfg.auto_reconnect is not None else _settings.persistent_auto_reconnect - ), - ) + config = merge_config_with_settings(PersistentConnectionConfig, config, _settings, _PERSISTENT_CONFIG_FIELD_MAP) # Build SSL connect_args from settings ssl_mode = _settings.ssl_mode @@ -960,19 +955,7 @@ def __new__( database = database or _settings.database connection_key = f"mysql://{user}@{host}:{port}/{database}" # NOSONAR - # Build config from settings, allowing partial overrides - _cfg = config or PersistentConnectionConfig() - config = PersistentConnectionConfig( - idle_timeout=_cfg.idle_timeout if _cfg.idle_timeout is not None else _settings.persistent_idle_timeout, - health_check_interval=( - _cfg.health_check_interval - if _cfg.health_check_interval is not None - else _settings.persistent_health_check_interval - ), - auto_reconnect=( - _cfg.auto_reconnect if _cfg.auto_reconnect is not None else _settings.persistent_auto_reconnect - ), - ) + config = merge_config_with_settings(PersistentConnectionConfig, config, _settings, _PERSISTENT_CONFIG_FIELD_MAP) # Build SSL connect_args from settings ssl_mode = _settings.ssl_mode @@ -1121,19 +1104,7 @@ def __new__( database = database or _settings.database connection_key = f"mssql://{user}@{host}:{port}/{database}" # NOSONAR - # Build config from settings, allowing partial overrides - _cfg = config or PersistentConnectionConfig() - config = PersistentConnectionConfig( - idle_timeout=_cfg.idle_timeout if _cfg.idle_timeout is not None else _settings.persistent_idle_timeout, - health_check_interval=( - _cfg.health_check_interval - if _cfg.health_check_interval is not None - else _settings.persistent_health_check_interval - ), - auto_reconnect=( - _cfg.auto_reconnect if _cfg.auto_reconnect is not None else _settings.persistent_auto_reconnect - ), - ) + config = merge_config_with_settings(PersistentConnectionConfig, config, _settings, _PERSISTENT_CONFIG_FIELD_MAP) # Build SSL query params from settings _query: dict[str, str] = {"driver": "ODBC Driver 18 for SQL Server"} @@ -1227,19 +1198,7 @@ def __new__( servicename = servicename or _settings.servicename connection_key = f"oracle://{user}@{host}:{port}/{servicename}" # NOSONAR - # Build config from settings, allowing partial overrides - _cfg = config or PersistentConnectionConfig() - config = PersistentConnectionConfig( - idle_timeout=_cfg.idle_timeout if _cfg.idle_timeout is not None else _settings.persistent_idle_timeout, - health_check_interval=( - _cfg.health_check_interval - if _cfg.health_check_interval is not None - else _settings.persistent_health_check_interval - ), - auto_reconnect=( - _cfg.auto_reconnect if _cfg.auto_reconnect is not None else _settings.persistent_auto_reconnect - ), - ) + config = merge_config_with_settings(PersistentConnectionConfig, config, _settings, _PERSISTENT_CONFIG_FIELD_MAP) with _registry_lock: if connection_key in _persistent_connections: @@ -1301,19 +1260,7 @@ def __new__( database = database or _settings.database connection_key = f"mongodb://{user}@{host}:{port}/{database}" # NOSONAR - # Build config from settings, allowing partial overrides - _cfg = config or PersistentConnectionConfig() - config = PersistentConnectionConfig( - idle_timeout=_cfg.idle_timeout if _cfg.idle_timeout is not None else _settings.persistent_idle_timeout, - health_check_interval=( - _cfg.health_check_interval - if _cfg.health_check_interval is not None - else _settings.persistent_health_check_interval - ), - auto_reconnect=( - _cfg.auto_reconnect if _cfg.auto_reconnect is not None else _settings.persistent_auto_reconnect - ), - ) + config = merge_config_with_settings(PersistentConnectionConfig, config, _settings, _PERSISTENT_CONFIG_FIELD_MAP) with _registry_lock: if connection_key in _persistent_connections: diff --git a/ddcdatabases/core/settings.py b/ddcdatabases/core/settings.py index f4f2f8b..1ad3ac9 100644 --- a/ddcdatabases/core/settings.py +++ b/ddcdatabases/core/settings.py @@ -41,6 +41,31 @@ class _BaseDBSettings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", extra="allow") +class _NetworkDBSettings(_BaseDBSettings): + """Base for network database settings with common retry and persistent fields.""" + + # Connection Retry settings + connection_enable_retry: bool = Field(default=True, description=Msg.ENABLE_RETRY_DESCRIPTION) + connection_max_retries: int = Field(default=3, description=Msg.MAX_RETRIES_DESCRIPTION) + connection_initial_retry_delay: float = Field(default=1.0, description=Msg.INITIAL_RETRY_DELAY_DESCRIPTION) + connection_max_retry_delay: float = Field(default=30.0, description=Msg.MAX_RETRY_DELAY_DESCRIPTION) + connection_disconnect_idle_timeout: int = Field(default=300, description=Msg.DISCONNECT_IDLE_TIMEOUT_DESCRIPTION) + + # Operation Retry settings + operation_enable_retry: bool = Field(default=True, description=Msg.ENABLE_RETRY_DESCRIPTION) + operation_max_retries: int = Field(default=3, description=Msg.MAX_RETRIES_DESCRIPTION) + operation_initial_retry_delay: float = Field(default=0.5, description=Msg.INITIAL_RETRY_DELAY_DESCRIPTION) + operation_max_retry_delay: float = Field(default=10.0, description=Msg.MAX_RETRY_DELAY_DESCRIPTION) + operation_jitter: float = Field(default=0.1, description=Msg.JITTER_DESCRIPTION) + + # Persistent connection settings + persistent_idle_timeout: int = Field(default=300, description=Msg.PERSISTENT_IDLE_TIMEOUT_DESCRIPTION) + persistent_health_check_interval: int = Field( + default=30, description=Msg.PERSISTENT_HEALTH_CHECK_INTERVAL_DESCRIPTION + ) + persistent_auto_reconnect: bool = Field(default=True, description=Msg.PERSISTENT_AUTO_RECONNECT_DESCRIPTION) + + class SQLiteSettings(_BaseDBSettings): """SQLite database settings with environment variable fallback.""" @@ -63,7 +88,7 @@ class SQLiteSettings(_BaseDBSettings): model_config = SettingsConfigDict(env_prefix="SQLITE_") -class PostgreSQLSettings(_BaseDBSettings): +class PostgreSQLSettings(_NetworkDBSettings): """PostgreSQL database settings with environment variable fallback.""" host: str = Field(default="localhost", description=Msg.HOST_DESCRIPTION) @@ -90,31 +115,10 @@ class PostgreSQLSettings(_BaseDBSettings): ssl_client_cert_path: str | None = Field(default=None, description=Msg.SSL_CLIENT_CERT_PATH_DESCRIPTION) ssl_client_key_path: str | None = Field(default=None, description=Msg.SSL_CLIENT_KEY_PATH_DESCRIPTION) - # Connection Retry settings - connection_enable_retry: bool = Field(default=True, description=Msg.ENABLE_RETRY_DESCRIPTION) - connection_max_retries: int = Field(default=3, description=Msg.MAX_RETRIES_DESCRIPTION) - connection_initial_retry_delay: float = Field(default=1.0, description=Msg.INITIAL_RETRY_DELAY_DESCRIPTION) - connection_max_retry_delay: float = Field(default=30.0, description=Msg.MAX_RETRY_DELAY_DESCRIPTION) - connection_disconnect_idle_timeout: int = Field(default=300, description=Msg.DISCONNECT_IDLE_TIMEOUT_DESCRIPTION) - - # Operation Retry settings - operation_enable_retry: bool = Field(default=True, description=Msg.ENABLE_RETRY_DESCRIPTION) - operation_max_retries: int = Field(default=3, description=Msg.MAX_RETRIES_DESCRIPTION) - operation_initial_retry_delay: float = Field(default=0.5, description=Msg.INITIAL_RETRY_DELAY_DESCRIPTION) - operation_max_retry_delay: float = Field(default=10.0, description=Msg.MAX_RETRY_DELAY_DESCRIPTION) - operation_jitter: float = Field(default=0.1, description=Msg.JITTER_DESCRIPTION) - - # Persistent connection settings - persistent_idle_timeout: int = Field(default=300, description=Msg.PERSISTENT_IDLE_TIMEOUT_DESCRIPTION) - persistent_health_check_interval: int = Field( - default=30, description=Msg.PERSISTENT_HEALTH_CHECK_INTERVAL_DESCRIPTION - ) - persistent_auto_reconnect: bool = Field(default=True, description=Msg.PERSISTENT_AUTO_RECONNECT_DESCRIPTION) - model_config = SettingsConfigDict(env_prefix="POSTGRESQL_") -class MSSQLSettings(_BaseDBSettings): +class MSSQLSettings(_NetworkDBSettings): """Microsoft SQL Server settings with environment variable fallback.""" host: str = Field(default="localhost", description=Msg.HOST_DESCRIPTION) @@ -141,31 +145,10 @@ class MSSQLSettings(_BaseDBSettings): ssl_trust_server_certificate: bool = Field(default=True, description=Msg.SSL_TRUST_SERVER_CERTIFICATE_DESCRIPTION) ssl_ca_cert_path: str | None = Field(default=None, description=Msg.SSL_CA_CERT_PATH_DESCRIPTION) - # Connection Retry settings - connection_enable_retry: bool = Field(default=True, description=Msg.ENABLE_RETRY_DESCRIPTION) - connection_max_retries: int = Field(default=3, description=Msg.MAX_RETRIES_DESCRIPTION) - connection_initial_retry_delay: float = Field(default=1.0, description=Msg.INITIAL_RETRY_DELAY_DESCRIPTION) - connection_max_retry_delay: float = Field(default=30.0, description=Msg.MAX_RETRY_DELAY_DESCRIPTION) - connection_disconnect_idle_timeout: int = Field(default=300, description=Msg.DISCONNECT_IDLE_TIMEOUT_DESCRIPTION) - - # Operation Retry settings - operation_enable_retry: bool = Field(default=True, description=Msg.ENABLE_RETRY_DESCRIPTION) - operation_max_retries: int = Field(default=3, description=Msg.MAX_RETRIES_DESCRIPTION) - operation_initial_retry_delay: float = Field(default=0.5, description=Msg.INITIAL_RETRY_DELAY_DESCRIPTION) - operation_max_retry_delay: float = Field(default=10.0, description=Msg.MAX_RETRY_DELAY_DESCRIPTION) - operation_jitter: float = Field(default=0.1, description=Msg.JITTER_DESCRIPTION) - - # Persistent connection settings - persistent_idle_timeout: int = Field(default=300, description=Msg.PERSISTENT_IDLE_TIMEOUT_DESCRIPTION) - persistent_health_check_interval: int = Field( - default=30, description=Msg.PERSISTENT_HEALTH_CHECK_INTERVAL_DESCRIPTION - ) - persistent_auto_reconnect: bool = Field(default=True, description=Msg.PERSISTENT_AUTO_RECONNECT_DESCRIPTION) - model_config = SettingsConfigDict(env_prefix="MSSQL_") -class MySQLSettings(_BaseDBSettings): +class MySQLSettings(_NetworkDBSettings): """MySQL database settings with environment variable fallback.""" host: str = Field(default="localhost", description=Msg.HOST_DESCRIPTION) @@ -191,31 +174,10 @@ class MySQLSettings(_BaseDBSettings): ssl_client_cert_path: str | None = Field(default=None, description=Msg.SSL_CLIENT_CERT_PATH_DESCRIPTION) ssl_client_key_path: str | None = Field(default=None, description=Msg.SSL_CLIENT_KEY_PATH_DESCRIPTION) - # Connection Retry settings - connection_enable_retry: bool = Field(default=True, description=Msg.ENABLE_RETRY_DESCRIPTION) - connection_max_retries: int = Field(default=3, description=Msg.MAX_RETRIES_DESCRIPTION) - connection_initial_retry_delay: float = Field(default=1.0, description=Msg.INITIAL_RETRY_DELAY_DESCRIPTION) - connection_max_retry_delay: float = Field(default=30.0, description=Msg.MAX_RETRY_DELAY_DESCRIPTION) - connection_disconnect_idle_timeout: int = Field(default=300, description=Msg.DISCONNECT_IDLE_TIMEOUT_DESCRIPTION) - - # Operation Retry settings - operation_enable_retry: bool = Field(default=True, description=Msg.ENABLE_RETRY_DESCRIPTION) - operation_max_retries: int = Field(default=3, description=Msg.MAX_RETRIES_DESCRIPTION) - operation_initial_retry_delay: float = Field(default=0.5, description=Msg.INITIAL_RETRY_DELAY_DESCRIPTION) - operation_max_retry_delay: float = Field(default=10.0, description=Msg.MAX_RETRY_DELAY_DESCRIPTION) - operation_jitter: float = Field(default=0.1, description=Msg.JITTER_DESCRIPTION) - - # Persistent connection settings - persistent_idle_timeout: int = Field(default=300, description=Msg.PERSISTENT_IDLE_TIMEOUT_DESCRIPTION) - persistent_health_check_interval: int = Field( - default=30, description=Msg.PERSISTENT_HEALTH_CHECK_INTERVAL_DESCRIPTION - ) - persistent_auto_reconnect: bool = Field(default=True, description=Msg.PERSISTENT_AUTO_RECONNECT_DESCRIPTION) - model_config = SettingsConfigDict(env_prefix="MYSQL_") -class MongoDBSettings(_BaseDBSettings): +class MongoDBSettings(_NetworkDBSettings): """MongoDB settings with environment variable fallback.""" host: str = Field(default="localhost", description=Msg.HOST_DESCRIPTION) @@ -236,31 +198,10 @@ class MongoDBSettings(_BaseDBSettings): default=False, description=Msg.TLS_ALLOW_INVALID_CERTIFICATES_DESCRIPTION ) - # Connection Retry settings - connection_enable_retry: bool = Field(default=True, description=Msg.ENABLE_RETRY_DESCRIPTION) - connection_max_retries: int = Field(default=3, description=Msg.MAX_RETRIES_DESCRIPTION) - connection_initial_retry_delay: float = Field(default=1.0, description=Msg.INITIAL_RETRY_DELAY_DESCRIPTION) - connection_max_retry_delay: float = Field(default=30.0, description=Msg.MAX_RETRY_DELAY_DESCRIPTION) - connection_disconnect_idle_timeout: int = Field(default=300, description=Msg.DISCONNECT_IDLE_TIMEOUT_DESCRIPTION) - - # Operation Retry settings - operation_enable_retry: bool = Field(default=True, description=Msg.ENABLE_RETRY_DESCRIPTION) - operation_max_retries: int = Field(default=3, description=Msg.MAX_RETRIES_DESCRIPTION) - operation_initial_retry_delay: float = Field(default=0.5, description=Msg.INITIAL_RETRY_DELAY_DESCRIPTION) - operation_max_retry_delay: float = Field(default=10.0, description=Msg.MAX_RETRY_DELAY_DESCRIPTION) - operation_jitter: float = Field(default=0.1, description=Msg.JITTER_DESCRIPTION) - - # Persistent connection settings - persistent_idle_timeout: int = Field(default=300, description=Msg.PERSISTENT_IDLE_TIMEOUT_DESCRIPTION) - persistent_health_check_interval: int = Field( - default=30, description=Msg.PERSISTENT_HEALTH_CHECK_INTERVAL_DESCRIPTION - ) - persistent_auto_reconnect: bool = Field(default=True, description=Msg.PERSISTENT_AUTO_RECONNECT_DESCRIPTION) - model_config = SettingsConfigDict(env_prefix="MONGODB_") -class OracleSettings(_BaseDBSettings): +class OracleSettings(_NetworkDBSettings): """Oracle database settings with environment variable fallback.""" host: str = Field(default="localhost", description=Msg.HOST_DESCRIPTION) @@ -283,27 +224,6 @@ class OracleSettings(_BaseDBSettings): ssl_enabled: bool = Field(default=False, description=Msg.SSL_ENABLED_DESCRIPTION) ssl_wallet_path: str | None = Field(default=None, description=Msg.SSL_WALLET_PATH_DESCRIPTION) - # Connection Retry settings - connection_enable_retry: bool = Field(default=True, description=Msg.ENABLE_RETRY_DESCRIPTION) - connection_max_retries: int = Field(default=3, description=Msg.MAX_RETRIES_DESCRIPTION) - connection_initial_retry_delay: float = Field(default=1.0, description=Msg.INITIAL_RETRY_DELAY_DESCRIPTION) - connection_max_retry_delay: float = Field(default=30.0, description=Msg.MAX_RETRY_DELAY_DESCRIPTION) - connection_disconnect_idle_timeout: int = Field(default=300, description=Msg.DISCONNECT_IDLE_TIMEOUT_DESCRIPTION) - - # Operation Retry settings - operation_enable_retry: bool = Field(default=True, description=Msg.ENABLE_RETRY_DESCRIPTION) - operation_max_retries: int = Field(default=3, description=Msg.MAX_RETRIES_DESCRIPTION) - operation_initial_retry_delay: float = Field(default=0.5, description=Msg.INITIAL_RETRY_DELAY_DESCRIPTION) - operation_max_retry_delay: float = Field(default=10.0, description=Msg.MAX_RETRY_DELAY_DESCRIPTION) - operation_jitter: float = Field(default=0.1, description=Msg.JITTER_DESCRIPTION) - - # Persistent connection settings - persistent_idle_timeout: int = Field(default=300, description=Msg.PERSISTENT_IDLE_TIMEOUT_DESCRIPTION) - persistent_health_check_interval: int = Field( - default=30, description=Msg.PERSISTENT_HEALTH_CHECK_INTERVAL_DESCRIPTION - ) - persistent_auto_reconnect: bool = Field(default=True, description=Msg.PERSISTENT_AUTO_RECONNECT_DESCRIPTION) - model_config = SettingsConfigDict(env_prefix="ORACLE_") diff --git a/ddcdatabases/mongodb.py b/ddcdatabases/mongodb.py index b7cc230..ecb6c48 100644 --- a/ddcdatabases/mongodb.py +++ b/ddcdatabases/mongodb.py @@ -1,6 +1,13 @@ import logging import sys -from .core.configs import BaseConnectionConfig, BaseOperationRetryConfig, BaseRetryConfig +from .core.configs import ( + CONNECTION_RETRY_FIELD_MAP, + OPERATION_RETRY_FIELD_MAP, + BaseConnectionConfig, + BaseOperationRetryConfig, + BaseRetryConfig, + merge_config_with_settings, +) from .core.retry import retry_operation, retry_operation_async from .core.settings import get_mongodb_settings from dataclasses import dataclass @@ -107,35 +114,11 @@ def __init__( self.cursor_ref = None self.async_cursor_ref = None - # Create connection retry configuration - _crc = connection_retry_config or MongoDBConnectionRetryConfig() - self._connection_retry_config = MongoDBConnectionRetryConfig( - enable_retry=_crc.enable_retry if _crc.enable_retry is not None else _settings.connection_enable_retry, - max_retries=_crc.max_retries if _crc.max_retries is not None else _settings.connection_max_retries, - initial_retry_delay=( - _crc.initial_retry_delay - if _crc.initial_retry_delay is not None - else _settings.connection_initial_retry_delay - ), - max_retry_delay=( - _crc.max_retry_delay if _crc.max_retry_delay is not None else _settings.connection_max_retry_delay - ), + self._connection_retry_config = merge_config_with_settings( + MongoDBConnectionRetryConfig, connection_retry_config, _settings, CONNECTION_RETRY_FIELD_MAP ) - - # Create operation retry configuration - _orc = operation_retry_config or MongoDBOperationRetryConfig() - self._operation_retry_config = MongoDBOperationRetryConfig( - enable_retry=_orc.enable_retry if _orc.enable_retry is not None else _settings.operation_enable_retry, - max_retries=_orc.max_retries if _orc.max_retries is not None else _settings.operation_max_retries, - initial_retry_delay=( - _orc.initial_retry_delay - if _orc.initial_retry_delay is not None - else _settings.operation_initial_retry_delay - ), - max_retry_delay=( - _orc.max_retry_delay if _orc.max_retry_delay is not None else _settings.operation_max_retry_delay - ), - jitter=_orc.jitter if _orc.jitter is not None else _settings.operation_jitter, + self._operation_retry_config = merge_config_with_settings( + MongoDBOperationRetryConfig, operation_retry_config, _settings, OPERATION_RETRY_FIELD_MAP ) self.logger = logger if logger is not None else _logger diff --git a/ddcdatabases/mssql.py b/ddcdatabases/mssql.py index 9cb9924..90f2a10 100755 --- a/ddcdatabases/mssql.py +++ b/ddcdatabases/mssql.py @@ -1,19 +1,17 @@ import logging -from .core.base import BaseConnection, ConnectionTester +from .core.base import BaseConnection from .core.configs import ( + CONNECTION_RETRY_FIELD_MAP, + OPERATION_RETRY_FIELD_MAP, BaseConnectionConfig, BaseOperationRetryConfig, BasePoolConfig, BaseRetryConfig, BaseSessionConfig, + merge_config_with_settings, ) from .core.settings import get_mssql_settings -from collections.abc import AsyncGenerator, Generator -from contextlib import asynccontextmanager, contextmanager from dataclasses import dataclass -from sqlalchemy.engine import URL, Engine, create_engine -from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine -from sqlalchemy.orm import Session from typing import Any _logger = logging.getLogger(__name__) @@ -87,23 +85,8 @@ def __init__( odbcdriver_version=int(_settings.odbcdriver_version), ) - _pc = pool_config or MSSQLPoolConfig() - self._pool_config = MSSQLPoolConfig( - pool_size=_pc.pool_size if _pc.pool_size is not None else int(_settings.pool_size), - max_overflow=_pc.max_overflow if _pc.max_overflow is not None else int(_settings.max_overflow), - pool_recycle=_pc.pool_recycle if _pc.pool_recycle is not None else _settings.pool_recycle, - connection_timeout=( - _pc.connection_timeout if _pc.connection_timeout is not None else _settings.connection_timeout - ), - ) - - _sc = session_config or MSSQLSessionConfig() - self._session_config = MSSQLSessionConfig( - echo=_sc.echo if _sc.echo is not None else _settings.echo, - autoflush=_sc.autoflush if _sc.autoflush is not None else _settings.autoflush, - expire_on_commit=_sc.expire_on_commit if _sc.expire_on_commit is not None else _settings.expire_on_commit, - autocommit=_sc.autocommit if _sc.autocommit is not None else _settings.autocommit, - ) + self._pool_config = merge_config_with_settings(MSSQLPoolConfig, pool_config, _settings) + self._session_config = merge_config_with_settings(MSSQLSessionConfig, session_config, _settings) _ssl = ssl_config or MSSQLSSLConfig() self._ssl_config = MSSQLSSLConfig( @@ -153,35 +136,11 @@ def __init__( **self.extra_engine_args, } - # Create connection retry configuration - _crc = connection_retry_config or MSSQLConnectionRetryConfig() - self._connection_retry_config = MSSQLConnectionRetryConfig( - enable_retry=_crc.enable_retry if _crc.enable_retry is not None else _settings.connection_enable_retry, - max_retries=_crc.max_retries if _crc.max_retries is not None else _settings.connection_max_retries, - initial_retry_delay=( - _crc.initial_retry_delay - if _crc.initial_retry_delay is not None - else _settings.connection_initial_retry_delay - ), - max_retry_delay=( - _crc.max_retry_delay if _crc.max_retry_delay is not None else _settings.connection_max_retry_delay - ), + self._connection_retry_config = merge_config_with_settings( + MSSQLConnectionRetryConfig, connection_retry_config, _settings, CONNECTION_RETRY_FIELD_MAP ) - - # Create operation retry configuration - _orc = operation_retry_config or MSSQLOperationRetryConfig() - self._operation_retry_config = MSSQLOperationRetryConfig( - enable_retry=_orc.enable_retry if _orc.enable_retry is not None else _settings.operation_enable_retry, - max_retries=_orc.max_retries if _orc.max_retries is not None else _settings.operation_max_retries, - initial_retry_delay=( - _orc.initial_retry_delay - if _orc.initial_retry_delay is not None - else _settings.operation_initial_retry_delay - ), - max_retry_delay=( - _orc.max_retry_delay if _orc.max_retry_delay is not None else _settings.operation_max_retry_delay - ), - jitter=_orc.jitter if _orc.jitter is not None else _settings.operation_jitter, + self._operation_retry_config = merge_config_with_settings( + MSSQLOperationRetryConfig, operation_retry_config, _settings, OPERATION_RETRY_FIELD_MAP ) self.logger = logger if logger is not None else _logger @@ -232,51 +191,3 @@ def get_operation_retry_info(self) -> MSSQLOperationRetryConfig: def get_ssl_info(self) -> MSSQLSSLConfig: return self._ssl_config - - @contextmanager - def _get_engine(self) -> Generator[Engine, None, None]: - _connection_url = URL.create( - drivername=self.sync_driver, - **self.connection_url, - ) - _engine = create_engine(url=_connection_url, **self.engine_args) - yield _engine - _engine.dispose() - - @asynccontextmanager - async def _get_async_engine(self) -> AsyncGenerator[AsyncEngine, None]: - _connection_url = URL.create( - drivername=self.async_driver, - **self.connection_url, - ) - _engine = create_async_engine(url=_connection_url, **self.engine_args) - yield _engine - await _engine.dispose() - - def _test_connection_sync(self, session: Session) -> None: - _connection_url_copy = self.connection_url.copy() - _connection_url_copy.pop("password", None) - _connection_url = URL.create( - **_connection_url_copy, - drivername=self.sync_driver, - ) - test_connection = ConnectionTester( - sync_session=session, - host_url=_connection_url, - logger=self.logger, - ) - test_connection.test_connection_sync() - - async def _test_connection_async(self, session: AsyncSession) -> None: - _connection_url_copy = self.connection_url.copy() - _connection_url_copy.pop("password", None) - _connection_url = URL.create( - **_connection_url_copy, - drivername=self.async_driver, - ) - test_connection = ConnectionTester( - async_session=session, - host_url=_connection_url, - logger=self.logger, - ) - await test_connection.test_connection_async() diff --git a/ddcdatabases/mysql.py b/ddcdatabases/mysql.py index 22c88b9..fb5951c 100755 --- a/ddcdatabases/mysql.py +++ b/ddcdatabases/mysql.py @@ -1,20 +1,19 @@ import logging from .core.base import BaseConnection from .core.configs import ( + CONNECTION_RETRY_FIELD_MAP, + OPERATION_RETRY_FIELD_MAP, BaseConnectionConfig, BaseOperationRetryConfig, BasePoolConfig, BaseRetryConfig, BaseSessionConfig, BaseSSLConfig, + merge_config_with_settings, ) from .core.constants import MYSQL_SSL_MODES from .core.settings import get_mysql_settings -from collections.abc import AsyncGenerator, Generator -from contextlib import asynccontextmanager, contextmanager from dataclasses import dataclass -from sqlalchemy.engine import URL, Engine, create_engine -from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine from typing import Any _logger = logging.getLogger(__name__) @@ -89,23 +88,8 @@ def __init__( database=database or _settings.database, ) - _pc = pool_config or MySQLPoolConfig() - self._pool_config = MySQLPoolConfig( - pool_size=_pc.pool_size if _pc.pool_size is not None else _settings.pool_size, - max_overflow=_pc.max_overflow if _pc.max_overflow is not None else _settings.max_overflow, - pool_recycle=_pc.pool_recycle if _pc.pool_recycle is not None else _settings.pool_recycle, - connection_timeout=( - _pc.connection_timeout if _pc.connection_timeout is not None else _settings.connection_timeout - ), - ) - - _sc = session_config or MySQLSessionConfig() - self._session_config = MySQLSessionConfig( - echo=_sc.echo if _sc.echo is not None else _settings.echo, - autoflush=_sc.autoflush if _sc.autoflush is not None else _settings.autoflush, - expire_on_commit=_sc.expire_on_commit if _sc.expire_on_commit is not None else _settings.expire_on_commit, - autocommit=_sc.autocommit if _sc.autocommit is not None else _settings.autocommit, - ) + self._pool_config = merge_config_with_settings(MySQLPoolConfig, pool_config, _settings) + self._session_config = merge_config_with_settings(MySQLSessionConfig, session_config, _settings) _ssl = ssl_config or MySQLSSLConfig() self._ssl_config = MySQLSSLConfig( @@ -155,35 +139,11 @@ def __init__( **self.extra_engine_args, } - # Create connection retry configuration - _crc = connection_retry_config or MySQLConnectionRetryConfig() - self._connection_retry_config = MySQLConnectionRetryConfig( - enable_retry=_crc.enable_retry if _crc.enable_retry is not None else _settings.connection_enable_retry, - max_retries=_crc.max_retries if _crc.max_retries is not None else _settings.connection_max_retries, - initial_retry_delay=( - _crc.initial_retry_delay - if _crc.initial_retry_delay is not None - else _settings.connection_initial_retry_delay - ), - max_retry_delay=( - _crc.max_retry_delay if _crc.max_retry_delay is not None else _settings.connection_max_retry_delay - ), + self._connection_retry_config = merge_config_with_settings( + MySQLConnectionRetryConfig, connection_retry_config, _settings, CONNECTION_RETRY_FIELD_MAP ) - - # Create operation retry configuration - _orc = operation_retry_config or MySQLOperationRetryConfig() - self._operation_retry_config = MySQLOperationRetryConfig( - enable_retry=_orc.enable_retry if _orc.enable_retry is not None else _settings.operation_enable_retry, - max_retries=_orc.max_retries if _orc.max_retries is not None else _settings.operation_max_retries, - initial_retry_delay=( - _orc.initial_retry_delay - if _orc.initial_retry_delay is not None - else _settings.operation_initial_retry_delay - ), - max_retry_delay=( - _orc.max_retry_delay if _orc.max_retry_delay is not None else _settings.operation_max_retry_delay - ), - jitter=_orc.jitter if _orc.jitter is not None else _settings.operation_jitter, + self._operation_retry_config = merge_config_with_settings( + MySQLOperationRetryConfig, operation_retry_config, _settings, OPERATION_RETRY_FIELD_MAP ) self.logger = logger if logger is not None else _logger @@ -234,31 +194,3 @@ def get_operation_retry_info(self) -> MySQLOperationRetryConfig: def get_ssl_info(self) -> MySQLSSLConfig: return self._ssl_config - - @contextmanager - def _get_engine(self) -> Generator[Engine, None, None]: - _connection_url = URL.create( - drivername=self.sync_driver, - **self.connection_url, - ) - _engine_args = { - "url": _connection_url, - **self.engine_args, - } - _engine = create_engine(**_engine_args) - yield _engine - _engine.dispose() - - @asynccontextmanager - async def _get_async_engine(self) -> AsyncGenerator[AsyncEngine, None]: - _connection_url = URL.create( - drivername=self.async_driver, - **self.connection_url, - ) - _engine_args = { - "url": _connection_url, - **self.engine_args, - } - _engine = create_async_engine(**_engine_args) - yield _engine - await _engine.dispose() diff --git a/ddcdatabases/oracle.py b/ddcdatabases/oracle.py index cf93697..f7a2f0a 100644 --- a/ddcdatabases/oracle.py +++ b/ddcdatabases/oracle.py @@ -1,17 +1,19 @@ import logging from .core.base import BaseConnection from .core.configs import ( + CONNECTION_RETRY_FIELD_MAP, + OPERATION_RETRY_FIELD_MAP, BaseConnectionConfig, BaseOperationRetryConfig, BasePoolConfig, BaseRetryConfig, BaseSessionConfig, + merge_config_with_settings, ) from .core.settings import get_oracle_settings -from collections.abc import AsyncGenerator, Generator -from contextlib import asynccontextmanager, contextmanager +from collections.abc import AsyncGenerator +from contextlib import asynccontextmanager from dataclasses import dataclass -from sqlalchemy.engine import URL, Engine, create_engine from sqlalchemy.ext.asyncio import AsyncEngine from typing import Any @@ -80,23 +82,8 @@ def __init__( servicename=servicename or _settings.servicename, ) - _pc = pool_config or OraclePoolConfig() - self._pool_config = OraclePoolConfig( - pool_size=_pc.pool_size if _pc.pool_size is not None else _settings.pool_size, - max_overflow=_pc.max_overflow if _pc.max_overflow is not None else _settings.max_overflow, - pool_recycle=_pc.pool_recycle if _pc.pool_recycle is not None else _settings.pool_recycle, - connection_timeout=( - _pc.connection_timeout if _pc.connection_timeout is not None else _settings.connection_timeout - ), - ) - - _sc = session_config or OracleSessionConfig() - self._session_config = OracleSessionConfig( - echo=_sc.echo if _sc.echo is not None else _settings.echo, - autoflush=_sc.autoflush if _sc.autoflush is not None else _settings.autoflush, - expire_on_commit=_sc.expire_on_commit if _sc.expire_on_commit is not None else _settings.expire_on_commit, - autocommit=_sc.autocommit if _sc.autocommit is not None else _settings.autocommit, - ) + self._pool_config = merge_config_with_settings(OraclePoolConfig, pool_config, _settings) + self._session_config = merge_config_with_settings(OracleSessionConfig, session_config, _settings) _ssl = ssl_config or OracleSSLConfig() self._ssl_config = OracleSSLConfig( @@ -130,35 +117,11 @@ def __init__( **self.extra_engine_args, } - # Create connection retry configuration - _crc = connection_retry_config or OracleConnectionRetryConfig() - self._connection_retry_config = OracleConnectionRetryConfig( - enable_retry=_crc.enable_retry if _crc.enable_retry is not None else _settings.connection_enable_retry, - max_retries=_crc.max_retries if _crc.max_retries is not None else _settings.connection_max_retries, - initial_retry_delay=( - _crc.initial_retry_delay - if _crc.initial_retry_delay is not None - else _settings.connection_initial_retry_delay - ), - max_retry_delay=( - _crc.max_retry_delay if _crc.max_retry_delay is not None else _settings.connection_max_retry_delay - ), + self._connection_retry_config = merge_config_with_settings( + OracleConnectionRetryConfig, connection_retry_config, _settings, CONNECTION_RETRY_FIELD_MAP ) - - # Create operation retry configuration - _orc = operation_retry_config or OracleOperationRetryConfig() - self._operation_retry_config = OracleOperationRetryConfig( - enable_retry=_orc.enable_retry if _orc.enable_retry is not None else _settings.operation_enable_retry, - max_retries=_orc.max_retries if _orc.max_retries is not None else _settings.operation_max_retries, - initial_retry_delay=( - _orc.initial_retry_delay - if _orc.initial_retry_delay is not None - else _settings.operation_initial_retry_delay - ), - max_retry_delay=( - _orc.max_retry_delay if _orc.max_retry_delay is not None else _settings.operation_max_retry_delay - ), - jitter=_orc.jitter if _orc.jitter is not None else _settings.operation_jitter, + self._operation_retry_config = merge_config_with_settings( + OracleOperationRetryConfig, operation_retry_config, _settings, OPERATION_RETRY_FIELD_MAP ) self.logger = logger if logger is not None else _logger @@ -210,20 +173,6 @@ def get_operation_retry_info(self) -> OracleOperationRetryConfig: def get_ssl_info(self) -> OracleSSLConfig: return self._ssl_config - @contextmanager - def _get_engine(self) -> Generator[Engine, None, None]: - _connection_url = URL.create( - drivername=self.sync_driver, - **self.connection_url, - ) - _engine_args = { - "url": _connection_url, - **self.engine_args, - } - _engine = create_engine(**_engine_args) - yield _engine - _engine.dispose() - @asynccontextmanager async def _get_async_engine(self) -> AsyncGenerator[AsyncEngine, None]: raise NotImplementedError("Oracle doesn't support async operations. Use synchronous methods only.") diff --git a/ddcdatabases/postgresql.py b/ddcdatabases/postgresql.py index 8c9ea28..627bfe5 100755 --- a/ddcdatabases/postgresql.py +++ b/ddcdatabases/postgresql.py @@ -2,12 +2,15 @@ import ssl as _ssl_module from .core.base import BaseConnection from .core.configs import ( + CONNECTION_RETRY_FIELD_MAP, + OPERATION_RETRY_FIELD_MAP, BaseConnectionConfig, BaseOperationRetryConfig, BasePoolConfig, BaseRetryConfig, BaseSessionConfig, BaseSSLConfig, + merge_config_with_settings, ) from .core.constants import POSTGRESQL_SSL_MODES from .core.settings import get_postgresql_settings @@ -88,23 +91,8 @@ def __init__( schema=schema if schema is not None else _settings.schema, ) - _pc = pool_config or PostgreSQLPoolConfig() - self._pool_config = PostgreSQLPoolConfig( - pool_size=_pc.pool_size if _pc.pool_size is not None else _settings.pool_size, - max_overflow=_pc.max_overflow if _pc.max_overflow is not None else _settings.max_overflow, - pool_recycle=_pc.pool_recycle if _pc.pool_recycle is not None else _settings.pool_recycle, - connection_timeout=( - _pc.connection_timeout if _pc.connection_timeout is not None else _settings.connection_timeout - ), - ) - - _sc = session_config or PostgreSQLSessionConfig() - self._session_config = PostgreSQLSessionConfig( - echo=_sc.echo if _sc.echo is not None else _settings.echo, - autoflush=_sc.autoflush if _sc.autoflush is not None else _settings.autoflush, - expire_on_commit=_sc.expire_on_commit if _sc.expire_on_commit is not None else _settings.expire_on_commit, - autocommit=_sc.autocommit if _sc.autocommit is not None else _settings.autocommit, - ) + self._pool_config = merge_config_with_settings(PostgreSQLPoolConfig, pool_config, _settings) + self._session_config = merge_config_with_settings(PostgreSQLSessionConfig, session_config, _settings) _ssl = ssl_config or PostgreSQLSSLConfig() self._ssl_config = PostgreSQLSSLConfig( @@ -137,35 +125,11 @@ def __init__( **self.extra_engine_args, } - # Create connection retry configuration - _crc = connection_retry_config or PostgreSQLConnectionRetryConfig() - self._connection_retry_config = PostgreSQLConnectionRetryConfig( - enable_retry=_crc.enable_retry if _crc.enable_retry is not None else _settings.connection_enable_retry, - max_retries=_crc.max_retries if _crc.max_retries is not None else _settings.connection_max_retries, - initial_retry_delay=( - _crc.initial_retry_delay - if _crc.initial_retry_delay is not None - else _settings.connection_initial_retry_delay - ), - max_retry_delay=( - _crc.max_retry_delay if _crc.max_retry_delay is not None else _settings.connection_max_retry_delay - ), + self._connection_retry_config = merge_config_with_settings( + PostgreSQLConnectionRetryConfig, connection_retry_config, _settings, CONNECTION_RETRY_FIELD_MAP ) - - # Create operation retry configuration - _orc = operation_retry_config or PostgreSQLOperationRetryConfig() - self._operation_retry_config = PostgreSQLOperationRetryConfig( - enable_retry=_orc.enable_retry if _orc.enable_retry is not None else _settings.operation_enable_retry, - max_retries=_orc.max_retries if _orc.max_retries is not None else _settings.operation_max_retries, - initial_retry_delay=( - _orc.initial_retry_delay - if _orc.initial_retry_delay is not None - else _settings.operation_initial_retry_delay - ), - max_retry_delay=( - _orc.max_retry_delay if _orc.max_retry_delay is not None else _settings.operation_max_retry_delay - ), - jitter=_orc.jitter if _orc.jitter is not None else _settings.operation_jitter, + self._operation_retry_config = merge_config_with_settings( + PostgreSQLOperationRetryConfig, operation_retry_config, _settings, OPERATION_RETRY_FIELD_MAP ) self.logger = logger if logger is not None else _logger diff --git a/ddcdatabases/sqlite.py b/ddcdatabases/sqlite.py index 054a692..f627607 100755 --- a/ddcdatabases/sqlite.py +++ b/ddcdatabases/sqlite.py @@ -1,5 +1,12 @@ import logging -from .core.configs import BaseOperationRetryConfig, BaseRetryConfig, BaseSessionConfig +from .core.configs import ( + CONNECTION_RETRY_FIELD_MAP, + OPERATION_RETRY_FIELD_MAP, + BaseOperationRetryConfig, + BaseRetryConfig, + BaseSessionConfig, + merge_config_with_settings, +) from .core.retry import retry_operation from .core.settings import get_sqlite_settings from dataclasses import dataclass @@ -59,35 +66,11 @@ def __init__( self.session: Session | None = None self._temp_engine: Engine | None = None - # Create connection retry configuration - _crc = connection_retry_config or SqliteConnectionRetryConfig() - self._connection_retry_config = SqliteConnectionRetryConfig( - enable_retry=_crc.enable_retry if _crc.enable_retry is not None else _settings.connection_enable_retry, - max_retries=_crc.max_retries if _crc.max_retries is not None else _settings.connection_max_retries, - initial_retry_delay=( - _crc.initial_retry_delay - if _crc.initial_retry_delay is not None - else _settings.connection_initial_retry_delay - ), - max_retry_delay=( - _crc.max_retry_delay if _crc.max_retry_delay is not None else _settings.connection_max_retry_delay - ), + self._connection_retry_config = merge_config_with_settings( + SqliteConnectionRetryConfig, connection_retry_config, _settings, CONNECTION_RETRY_FIELD_MAP ) - - # Create operation retry configuration - _orc = operation_retry_config or SqliteOperationRetryConfig() - self._operation_retry_config = SqliteOperationRetryConfig( - enable_retry=_orc.enable_retry if _orc.enable_retry is not None else _settings.operation_enable_retry, - max_retries=_orc.max_retries if _orc.max_retries is not None else _settings.operation_max_retries, - initial_retry_delay=( - _orc.initial_retry_delay - if _orc.initial_retry_delay is not None - else _settings.operation_initial_retry_delay - ), - max_retry_delay=( - _orc.max_retry_delay if _orc.max_retry_delay is not None else _settings.operation_max_retry_delay - ), - jitter=_orc.jitter if _orc.jitter is not None else _settings.operation_jitter, + self._operation_retry_config = merge_config_with_settings( + SqliteOperationRetryConfig, operation_retry_config, _settings, OPERATION_RETRY_FIELD_MAP ) self.logger = logger if logger is not None else _logger From adf169a60f40290f84e52fe62676ed9d7641289c Mon Sep 17 00:00:00 2001 From: ddc <34492089+ddc@users.noreply.github.com> Date: Tue, 17 Mar 2026 11:41:37 -0300 Subject: [PATCH 5/5] v4.0.0 --- README.md | 70 ++++++------- ddcdatabases/__init__.py | 2 +- pyproject.toml | 6 +- uv.lock | 216 +++++++++++++++++++-------------------- 4 files changed, 147 insertions(+), 147 deletions(-) diff --git a/README.md b/README.md index d685d4d..7526188 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

- ddcDatabases + ddcdatabases
- ddcDatabases + ddcdatabases

@@ -83,7 +83,7 @@ | MySQL | `True` | Autocommit ON is MySQL's default | | Oracle | `False` | Requires explicit COMMIT | -**Note:** All constructor parameters are optional and fall back to [.env](./ddcDatabases/.env.example) file variables. +**Note:** All constructor parameters are optional and fall back to [.env](./ddcdatabases/.env.example) file variables. ## Configuration Classes @@ -123,7 +123,7 @@ Retry with exponential backoff is enabled by default at two levels: **1. Connection Level** - Retries when establishing database connections: ```python -from ddcDatabases import PostgreSQL, PostgreSQLConnectionRetryConfig +from ddcdatabases import PostgreSQL, PostgreSQLConnectionRetryConfig with PostgreSQL( connection_retry_config=PostgreSQLConnectionRetryConfig( @@ -139,7 +139,7 @@ with PostgreSQL( **2. Operation Level** - Retries individual database operations (fetchall, insert, etc.): ```python -from ddcDatabases import DBUtils, PostgreSQL, PostgreSQLOperationRetryConfig +from ddcdatabases import DBUtils, PostgreSQL, PostgreSQLOperationRetryConfig with PostgreSQL( operation_retry_config=PostgreSQLOperationRetryConfig( @@ -172,7 +172,7 @@ with PostgreSQL( For long-running applications, use persistent connections with automatic reconnection and idle timeout: ```python -from ddcDatabases import ( +from ddcdatabases import ( PostgreSQLPersistent, MySQLPersistent, MongoDBPersistent, @@ -232,7 +232,7 @@ The `execute_with_retry` method provides automatic session management with retry **Synchronous:** ```python -from ddcDatabases import PostgreSQLPersistent +from ddcdatabases import PostgreSQLPersistent db = PostgreSQLPersistent(logger=logger) result = db.execute_with_retry( @@ -242,7 +242,7 @@ result = db.execute_with_retry( **Asynchronous:** ```python -from ddcDatabases import PostgreSQLPersistent +from ddcdatabases import PostgreSQLPersistent db = PostgreSQLPersistent(async_mode=True, logger=logger) result = await db.execute_with_retry( @@ -269,7 +269,7 @@ The method automatically: ## Basic Installation (SQLite only) ```shell -pip install ddcDatabases +pip install ddcdatabases ``` **Note:** The basic installation includes only SQlite. Database-specific drivers are optional extras that you can install as needed. @@ -280,29 +280,29 @@ Install only the database drivers you need: ```shell # All database drivers -pip install "ddcDatabases[all]" +pip install "ddcdatabases[all]" # SQL Server / MSSQL -pip install "ddcDatabases[mssql]" +pip install "ddcdatabases[mssql]" # MySQL/MariaDB -pip install "ddcDatabases[mysql]" +pip install "ddcdatabases[mysql]" # or -pip install "ddcDatabases[mariadb]" +pip install "ddcdatabases[mariadb]" # PostgreSQL -pip install "ddcDatabases[postgres]" +pip install "ddcdatabases[postgres]" # or -pip install "ddcDatabases[pgsql]" +pip install "ddcdatabases[pgsql]" # Oracle Database -pip install "ddcDatabases[oracle]" +pip install "ddcdatabases[oracle]" # MongoDB -pip install "ddcDatabases[mongodb]" +pip install "ddcdatabases[mongodb]" # Multiple databases (example) -pip install "ddcDatabases[mysql,postgres,mongodb]" +pip install "ddcdatabases[mysql,postgres,mongodb]" ``` **Available Database Extras:** @@ -331,7 +331,7 @@ pip install "ddcDatabases[mysql,postgres,mongodb]" ```python import sqlalchemy as sa -from ddcDatabases import DBUtils, Sqlite +from ddcdatabases import DBUtils, Sqlite from your_models import Model # Your SQLAlchemy model with Sqlite(filepath="data.db") as session: @@ -349,7 +349,7 @@ with Sqlite(filepath="data.db") as session: ```python import sqlalchemy as sa -from ddcDatabases import DBUtils, MSSQL, MSSQLPoolConfig, MSSQLSessionConfig, MSSQLSSLConfig +from ddcdatabases import DBUtils, MSSQL, MSSQLPoolConfig, MSSQLSessionConfig, MSSQLSSLConfig with MSSQL( host="127.0.0.1", @@ -387,7 +387,7 @@ with MSSQL( ```python import asyncio import sqlalchemy as sa -from ddcDatabases import DBUtilsAsync, MSSQL +from ddcdatabases import DBUtilsAsync, MSSQL from your_models import Model async def main(): @@ -407,7 +407,7 @@ asyncio.run(main()) ```python import sqlalchemy as sa -from ddcDatabases import DBUtils, PostgreSQL, PostgreSQLPoolConfig, PostgreSQLSessionConfig, PostgreSQLSSLConfig +from ddcdatabases import DBUtils, PostgreSQL, PostgreSQLPoolConfig, PostgreSQLSessionConfig, PostgreSQLSSLConfig with PostgreSQL( host="127.0.0.1", @@ -447,7 +447,7 @@ with PostgreSQL( ```python import asyncio import sqlalchemy as sa -from ddcDatabases import DBUtilsAsync, PostgreSQL +from ddcdatabases import DBUtilsAsync, PostgreSQL from your_models import Model async def main(): @@ -467,15 +467,15 @@ The MySQL class is fully compatible with both MySQL and MariaDB databases. For c ```python # Both imports are equivalent -from ddcDatabases import MySQL, MySQLPoolConfig, MySQLSessionConfig -from ddcDatabases import MariaDB, MariaDBPoolConfig, MariaDBSessionConfig # Aliases +from ddcdatabases import MySQL, MySQLPoolConfig, MySQLSessionConfig +from ddcdatabases import MariaDB, MariaDBPoolConfig, MariaDBSessionConfig # Aliases ``` **Synchronous Example:** ```python import sqlalchemy as sa -from ddcDatabases import DBUtils, MySQL, MySQLPoolConfig, MySQLSessionConfig, MySQLSSLConfig +from ddcdatabases import DBUtils, MySQL, MySQLPoolConfig, MySQLSessionConfig, MySQLSSLConfig with MySQL( host="127.0.0.1", @@ -514,7 +514,7 @@ with MySQL( ```python import asyncio import sqlalchemy as sa -from ddcDatabases import DBUtilsAsync, MySQL +from ddcdatabases import DBUtilsAsync, MySQL async def main() -> None: async with MySQL(host="127.0.0.1", database="dev") as session: @@ -533,7 +533,7 @@ asyncio.run(main()) ```python import sqlalchemy as sa -from ddcDatabases import DBUtils, Oracle, OraclePoolConfig, OracleSessionConfig, OracleSSLConfig +from ddcdatabases import DBUtils, Oracle, OraclePoolConfig, OracleSessionConfig, OracleSSLConfig with Oracle( host="127.0.0.1", @@ -574,7 +574,7 @@ with Oracle( **Example:** ```python -from ddcDatabases import MongoDB, MongoDBQueryConfig, MongoDBTLSConfig +from ddcdatabases import MongoDB, MongoDBQueryConfig, MongoDBTLSConfig from bson.objectid import ObjectId with MongoDB( @@ -611,7 +611,7 @@ Access the underlying SQLAlchemy engine for advanced operations: **Synchronous Engine:** ```python -from ddcDatabases import PostgreSQL +from ddcdatabases import PostgreSQL with PostgreSQL() as session: engine = session.bind @@ -622,7 +622,7 @@ with PostgreSQL() as session: ```python import asyncio -from ddcDatabases import PostgreSQL +from ddcdatabases import PostgreSQL async def main(): async with PostgreSQL() as session: @@ -640,7 +640,7 @@ The `DBUtils` and `DBUtilsAsync` classes provide convenient methods for common d ## Available Methods ```python -from ddcDatabases import DBUtils, DBUtilsAsync, PostgreSQL +from ddcdatabases import DBUtils, DBUtilsAsync, PostgreSQL # Synchronous utilities with PostgreSQL() as session: @@ -670,7 +670,7 @@ All database classes accept an optional `logger` parameter. By default, logs are ```python import logging -from ddcDatabases import PostgreSQL, DBUtils +from ddcdatabases import PostgreSQL, DBUtils log = logging.getLogger("myapp") log.setLevel(logging.DEBUG) @@ -685,8 +685,8 @@ with PostgreSQL(host="localhost", database="mydb", logger=log) as session: ```python import logging -logging.getLogger("ddcDatabases").setLevel(logging.DEBUG) -logging.getLogger("ddcDatabases").addHandler(logging.StreamHandler()) +logging.getLogger("ddcdatabases").setLevel(logging.DEBUG) +logging.getLogger("ddcdatabases").addHandler(logging.StreamHandler()) ``` diff --git a/ddcdatabases/__init__.py b/ddcdatabases/__init__.py index a4df87a..7192be9 100755 --- a/ddcdatabases/__init__.py +++ b/ddcdatabases/__init__.py @@ -193,7 +193,7 @@ pass __all__ = tuple(__all__) -__title__ = "ddcDatabases" +__title__ = "ddcdatabases" __author__ = "Daniel Costa" __email__ = "daniel@ddcsoftwares.com" __license__ = "MIT" diff --git a/pyproject.toml b/pyproject.toml index 52713b9..89b869b 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ include = ["ddcdatabases/**/*"] packages = ["ddcdatabases"] [project] -name = "ddcDatabases" +name = "ddcdatabases" version = "4.0.0" description = "Simplified database ORM connections with support for multiple database engines" urls.Repository = "https://github.com/ddc/ddcDatabases" @@ -28,7 +28,7 @@ maintainers = [ keywords = [ "python", "python3", "python-3", "ORM", "ORM-connection", "sqlalchemy", - "database", "databases", "ddcDatabase", "ddcDatabases", + "database", "databases", "ddcdatabase", "ddcdatabases", "mssql", "mssql-database", "mysql", "mysql-database", "oracle", "oracle-database", @@ -69,7 +69,7 @@ mariadb = ["ddcdatabases[mysql]"] [dependency-groups] dev = [ - "coverage>=7.13.4", + "coverage>=7.13.5", "poethepoet>=0.42.1", "pytest-asyncio>=1.3.0", "ruff>=0.15.6", diff --git a/uv.lock b/uv.lock index 7d5791a..a49a348 100644 --- a/uv.lock +++ b/uv.lock @@ -319,115 +319,115 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.4" +version = "7.13.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/d4/7827d9ffa34d5d4d752eec907022aa417120936282fc488306f5da08c292/coverage-7.13.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fc31c787a84f8cd6027eba44010517020e0d18487064cd3d8968941856d1415", size = 219152, upload-time = "2026-02-09T12:56:11.974Z" }, - { url = "https://files.pythonhosted.org/packages/35/b0/d69df26607c64043292644dbb9dc54b0856fabaa2cbb1eeee3331cc9e280/coverage-7.13.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a32ebc02a1805adf637fc8dec324b5cdacd2e493515424f70ee33799573d661b", size = 219667, upload-time = "2026-02-09T12:56:13.33Z" }, - { url = "https://files.pythonhosted.org/packages/82/a4/c1523f7c9e47b2271dbf8c2a097e7a1f89ef0d66f5840bb59b7e8814157b/coverage-7.13.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e24f9156097ff9dc286f2f913df3a7f63c0e333dcafa3c196f2c18b4175ca09a", size = 246425, upload-time = "2026-02-09T12:56:14.552Z" }, - { url = "https://files.pythonhosted.org/packages/f8/02/aa7ec01d1a5023c4b680ab7257f9bfde9defe8fdddfe40be096ac19e8177/coverage-7.13.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8041b6c5bfdc03257666e9881d33b1abc88daccaf73f7b6340fb7946655cd10f", size = 248229, upload-time = "2026-02-09T12:56:16.31Z" }, - { url = "https://files.pythonhosted.org/packages/35/98/85aba0aed5126d896162087ef3f0e789a225697245256fc6181b95f47207/coverage-7.13.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a09cfa6a5862bc2fc6ca7c3def5b2926194a56b8ab78ffcf617d28911123012", size = 250106, upload-time = "2026-02-09T12:56:18.024Z" }, - { url = "https://files.pythonhosted.org/packages/96/72/1db59bd67494bc162e3e4cd5fbc7edba2c7026b22f7c8ef1496d58c2b94c/coverage-7.13.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:296f8b0af861d3970c2a4d8c91d48eb4dd4771bcef9baedec6a9b515d7de3def", size = 252021, upload-time = "2026-02-09T12:56:19.272Z" }, - { url = "https://files.pythonhosted.org/packages/9d/97/72899c59c7066961de6e3daa142d459d47d104956db43e057e034f015c8a/coverage-7.13.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e101609bcbbfb04605ea1027b10dc3735c094d12d40826a60f897b98b1c30256", size = 247114, upload-time = "2026-02-09T12:56:21.051Z" }, - { url = "https://files.pythonhosted.org/packages/39/1f/f1885573b5970235e908da4389176936c8933e86cb316b9620aab1585fa2/coverage-7.13.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aa3feb8db2e87ff5e6d00d7e1480ae241876286691265657b500886c98f38bda", size = 248143, upload-time = "2026-02-09T12:56:22.585Z" }, - { url = "https://files.pythonhosted.org/packages/a8/cf/e80390c5b7480b722fa3e994f8202807799b85bc562aa4f1dde209fbb7be/coverage-7.13.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4fc7fa81bbaf5a02801b65346c8b3e657f1d93763e58c0abdf7c992addd81a92", size = 246152, upload-time = "2026-02-09T12:56:23.748Z" }, - { url = "https://files.pythonhosted.org/packages/44/bf/f89a8350d85572f95412debb0fb9bb4795b1d5b5232bd652923c759e787b/coverage-7.13.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:33901f604424145c6e9c2398684b92e176c0b12df77d52db81c20abd48c3794c", size = 249959, upload-time = "2026-02-09T12:56:25.209Z" }, - { url = "https://files.pythonhosted.org/packages/f7/6e/612a02aece8178c818df273e8d1642190c4875402ca2ba74514394b27aba/coverage-7.13.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:bb28c0f2cf2782508a40cec377935829d5fcc3ad9a3681375af4e84eb34b6b58", size = 246416, upload-time = "2026-02-09T12:56:26.475Z" }, - { url = "https://files.pythonhosted.org/packages/cb/98/b5afc39af67c2fa6786b03c3a7091fc300947387ce8914b096db8a73d67a/coverage-7.13.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d107aff57a83222ddbd8d9ee705ede2af2cc926608b57abed8ef96b50b7e8f9", size = 247025, upload-time = "2026-02-09T12:56:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/51/30/2bba8ef0682d5bd210c38fe497e12a06c9f8d663f7025e9f5c2c31ce847d/coverage-7.13.4-cp310-cp310-win32.whl", hash = "sha256:a6f94a7d00eb18f1b6d403c91a88fd58cfc92d4b16080dfdb774afc8294469bf", size = 221758, upload-time = "2026-02-09T12:56:29.051Z" }, - { url = "https://files.pythonhosted.org/packages/78/13/331f94934cf6c092b8ea59ff868eb587bc8fe0893f02c55bc6c0183a192e/coverage-7.13.4-cp310-cp310-win_amd64.whl", hash = "sha256:2cb0f1e000ebc419632bbe04366a8990b6e32c4e0b51543a6484ffe15eaeda95", size = 222693, upload-time = "2026-02-09T12:56:30.366Z" }, - { url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" }, - { url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" }, - { url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" }, - { url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" }, - { url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" }, - { url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" }, - { url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" }, - { url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" }, - { url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" }, - { url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" }, - { url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" }, - { url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" }, - { url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" }, - { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, - { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, - { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, - { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, - { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, - { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, - { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, - { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, - { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, - { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, - { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, - { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, - { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, - { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, - { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, - { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, - { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, - { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, - { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, - { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, - { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, - { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, - { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, - { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, - { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, - { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, - { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, - { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, - { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, - { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, - { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, - { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, - { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, - { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, - { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, - { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, - { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, - { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, - { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, - { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, - { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, - { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, - { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, - { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, - { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, - { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, - { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, - { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, - { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, - { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, - { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, - { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, - { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, - { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, - { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, - { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, - { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, - { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, - { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, - { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, - { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, - { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, + { url = "https://files.pythonhosted.org/packages/69/33/e8c48488c29a73fd089f9d71f9653c1be7478f2ad6b5bc870db11a55d23d/coverage-7.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0723d2c96324561b9aa76fb982406e11d93cdb388a7a7da2b16e04719cf7ca5", size = 219255, upload-time = "2026-03-17T10:29:51.081Z" }, + { url = "https://files.pythonhosted.org/packages/da/bd/b0ebe9f677d7f4b74a3e115eec7ddd4bcf892074963a00d91e8b164a6386/coverage-7.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52f444e86475992506b32d4e5ca55c24fc88d73bcbda0e9745095b28ef4dc0cf", size = 219772, upload-time = "2026-03-17T10:29:52.867Z" }, + { url = "https://files.pythonhosted.org/packages/48/cc/5cb9502f4e01972f54eedd48218bb203fe81e294be606a2bc93970208013/coverage-7.13.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:704de6328e3d612a8f6c07000a878ff38181ec3263d5a11da1db294fa6a9bdf8", size = 246532, upload-time = "2026-03-17T10:29:54.688Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d8/3217636d86c7e7b12e126e4f30ef1581047da73140614523af7495ed5f2d/coverage-7.13.5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a1a6d79a14e1ec1832cabc833898636ad5f3754a678ef8bb4908515208bf84f4", size = 248333, upload-time = "2026-03-17T10:29:56.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/30/2002ac6729ba2d4357438e2ed3c447ad8562866c8c63fc16f6dfc33afe56/coverage-7.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79060214983769c7ba3f0cee10b54c97609dca4d478fa1aa32b914480fd5738d", size = 250211, upload-time = "2026-03-17T10:29:57.938Z" }, + { url = "https://files.pythonhosted.org/packages/6c/85/552496626d6b9359eb0e2f86f920037c9cbfba09b24d914c6e1528155f7d/coverage-7.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:356e76b46783a98c2a2fe81ec79df4883a1e62895ea952968fb253c114e7f930", size = 252125, upload-time = "2026-03-17T10:29:59.388Z" }, + { url = "https://files.pythonhosted.org/packages/44/21/40256eabdcbccdb6acf6b381b3016a154399a75fe39d406f790ae84d1f3c/coverage-7.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cef0cdec915d11254a7f549c1170afecce708d30610c6abdded1f74e581666d", size = 247219, upload-time = "2026-03-17T10:30:01.199Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/96e2a6c3f21a0ea77d7830b254a1542d0328acc8d7bdf6a284ba7e529f77/coverage-7.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc022073d063b25a402454e5712ef9e007113e3a676b96c5f29b2bda29352f40", size = 248248, upload-time = "2026-03-17T10:30:03.317Z" }, + { url = "https://files.pythonhosted.org/packages/da/ba/8477f549e554827da390ec659f3c38e4b6d95470f4daafc2d8ff94eaa9c2/coverage-7.13.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9b74db26dfea4f4e50d48a4602207cd1e78be33182bc9cbf22da94f332f99878", size = 246254, upload-time = "2026-03-17T10:30:04.832Z" }, + { url = "https://files.pythonhosted.org/packages/55/59/bc22aef0e6aa179d5b1b001e8b3654785e9adf27ef24c93dc4228ebd5d68/coverage-7.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ad146744ca4fd09b50c482650e3c1b1f4dfa1d4792e0a04a369c7f23336f0400", size = 250067, upload-time = "2026-03-17T10:30:06.535Z" }, + { url = "https://files.pythonhosted.org/packages/de/1b/c6a023a160806a5137dca53468fd97530d6acad24a22003b1578a9c2e429/coverage-7.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c555b48be1853fe3997c11c4bd521cdd9a9612352de01fa4508f16ec341e6fe0", size = 246521, upload-time = "2026-03-17T10:30:08.486Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3f/3532c85a55aa2f899fa17c186f831cfa1aa434d88ff792a709636f64130e/coverage-7.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7034b5c56a58ae5e85f23949d52c14aca2cfc6848a31764995b7de88f13a1ea0", size = 247126, upload-time = "2026-03-17T10:30:09.966Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2e/b9d56af4a24ef45dfbcda88e06870cb7d57b2b0bfa3a888d79b4c8debd76/coverage-7.13.5-cp310-cp310-win32.whl", hash = "sha256:eb7fdf1ef130660e7415e0253a01a7d5a88c9c4d158bcf75cbbd922fd65a5b58", size = 221860, upload-time = "2026-03-17T10:30:11.393Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cc/d938417e7a4d7f0433ad4edee8bb2acdc60dc7ac5af19e2a07a048ecbee3/coverage-7.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:3e1bb5f6c78feeb1be3475789b14a0f0a5b47d505bfc7267126ccbd50289999e", size = 222788, upload-time = "2026-03-17T10:30:12.886Z" }, + { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" }, + { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, + { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" }, + { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" }, + { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" }, + { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" }, + { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f6/d0fd2d21e29a657b5f77a2fe7082e1568158340dceb941954f776dce1b7b/coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a", size = 250080, upload-time = "2026-03-17T10:30:29.481Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" }, + { url = "https://files.pythonhosted.org/packages/67/21/9ac389377380a07884e3b48ba7a620fcd9dbfaf1d40565facdc6b36ec9ef/coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf", size = 221880, upload-time = "2026-03-17T10:30:36.775Z" }, + { url = "https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9", size = 222816, upload-time = "2026-03-17T10:30:38.891Z" }, + { url = "https://files.pythonhosted.org/packages/12/a6/1d3f6155fb0010ca68eba7fe48ca6c9da7385058b77a95848710ecf189b1/coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028", size = 221483, upload-time = "2026-03-17T10:30:40.463Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, + { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, + { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, + { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, + { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, + { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, + { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, + { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, + { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, + { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, + { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, + { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, + { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, + { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, + { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, + { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, + { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, + { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, + { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, + { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, + { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, + { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, + { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, + { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, + { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, ] [[package]] @@ -555,7 +555,7 @@ provides-extras = ["mongodb", "oracle", "mssql", "mysql", "postgres", "pgsql", " [package.metadata.requires-dev] dev = [ - { name = "coverage", specifier = ">=7.13.4" }, + { name = "coverage", specifier = ">=7.13.5" }, { name = "poethepoet", specifier = ">=0.42.1" }, { name = "pytest-asyncio", specifier = ">=1.3.0" }, { name = "ruff", specifier = ">=0.15.6" },