This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-release.git


The following commit(s) were added to refs/heads/main by this push:
     new 910e8a9  Improve some utilities associated with importing keys
910e8a9 is described below

commit 910e8a9a8a3d199f6ebbb018802b0864fba4ae5c
Author: Sean B. Palmer <[email protected]>
AuthorDate: Fri Jun 13 20:26:12 2025 +0100

    Improve some utilities associated with importing keys
---
 atr/db/interaction.py |  28 +----------
 atr/util.py           |  72 ++++++++++++++++++++++++++
 poetry.lock           | 136 +++++++++++++++++++++++++++++++++++++++++++++++++-
 pyproject.toml        |   1 +
 4 files changed, 209 insertions(+), 28 deletions(-)

diff --git a/atr/db/interaction.py b/atr/db/interaction.py
index 193e635..7360a37 100644
--- a/atr/db/interaction.py
+++ b/atr/db/interaction.py
@@ -31,7 +31,6 @@ import sqlmodel
 import atr.analysis as analysis
 import atr.db as db
 import atr.db.models as models
-import atr.ldap as ldap
 import atr.schema as schema
 import atr.user as user
 import atr.util as util
@@ -78,18 +77,7 @@ async def key_user_add(asf_uid: str | None, public_key: str, 
selected_committees
 
     added_keys = []
     for key in keys:
-        # Determine ASF UID if not provided
-        if asf_uid is None:
-            for uid_str in key["uids"]:
-                if match := re.search(r"([A-Za-z0-9]+)@apache.org", uid_str):
-                    asf_uid = match.group(1).lower()
-                    break
-            else:
-                # _LOGGER.warning(f"key_user_add called with no ASF UID found 
in key UIDs: {key.get('uids')}")
-                for uid_str in key.get("uids", []):
-                    if asf_uid := await 
asyncio.to_thread(_asf_uid_from_uid_str, uid_str):
-                        break
-
+        asf_uid = await util.asf_uid_from_uids(key.get("uids", []))
         # Store key in database
         async with db.session() as data:
             added = await key_user_session_add(asf_uid, public_key, key, 
selected_committees, data)
@@ -316,20 +304,6 @@ async def upload_keys(
     return results, success_count, error_count, submitted_committees
 
 
-def _asf_uid_from_uid_str(uid_str: str) -> str | None:
-    if not (email_match := re.search(r"<([^>]+)>", uid_str)):
-        return None
-    email = email_match.group(1)
-    if email.endswith("@apache.org"):
-        return None
-    ldap_params = ldap.SearchParameters(email_query=email)
-    ldap.search(ldap_params)
-    if not (ldap_params.results_list and ("uid" in 
ldap_params.results_list[0])):
-        return None
-    ldap_uid_val = ldap_params.results_list[0]["uid"]
-    return ldap_uid_val[0] if isinstance(ldap_uid_val, list) else ldap_uid_val
-
-
 def _key_latest_self_signature(key: dict) -> datetime.datetime | None:
     fingerprint = key["fingerprint"]
     # TODO: Only 64 bits, which is not at all secure
diff --git a/atr/util.py b/atr/util.py
index 98f7923..114b85c 100644
--- a/atr/util.py
+++ b/atr/util.py
@@ -37,6 +37,7 @@ import aioshutil
 import asfquart
 import asfquart.base as base
 import asfquart.session as session
+import httpx
 import jinja2
 import quart
 import quart_wtf
@@ -47,6 +48,7 @@ import wtforms
 # Therefore, this module must not import atr.db
 import atr.config as config
 import atr.db.models as models
+import atr.ldap as ldap
 import atr.user as user
 
 F = TypeVar("F", bound="QuartFormTyped")
@@ -139,6 +141,32 @@ def as_url(func: Callable, **kwargs: Any) -> str:
     return quart.url_for(annotations["endpoint"], **kwargs)
 
 
+def asf_uid_from_email(email: str) -> str | None:
+    ldap_params = ldap.SearchParameters(email_query=email)
+    ldap.search(ldap_params)
+    if not (ldap_params.results_list and ("uid" in 
ldap_params.results_list[0])):
+        return None
+    ldap_uid_val = ldap_params.results_list[0]["uid"]
+    return ldap_uid_val[0] if isinstance(ldap_uid_val, list) else ldap_uid_val
+
+
+async def asf_uid_from_uids(uids: list[str]) -> str | None:
+    # Determine ASF UID if not provided
+    emails = []
+    for uid_str in uids:
+        if match := re.search(r"<([^>]+)>", uid_str):
+            email = match.group(1).lower()
+            if email.endswith("@apache.org"):
+                return email.removesuffix("@apache.org")
+            emails.append(email)
+    # We did not find a direct @apache.org email address
+    # Therefore, search LDAP
+    for email in emails:
+        if asf_uid := await asyncio.to_thread(asf_uid_from_email, email):
+            return asf_uid
+    return None
+
+
 @contextlib.asynccontextmanager
 async def async_temporary_directory(
     suffix: str | None = None, prefix: str | None = None, dir: str | 
pathlib.Path | None = None
@@ -373,6 +401,30 @@ def get_unfinished_dir() -> pathlib.Path:
     return pathlib.Path(config.get().UNFINISHED_STORAGE_DIR)
 
 
+async def get_urls_as_completed(urls: Sequence[str]) -> 
AsyncGenerator[tuple[str, int | str | None, bytes]]:
+    """GET a list of URLs in parallel and yield (url, status, content_bytes) 
as they become available."""
+    async with httpx.AsyncClient() as client:
+        tasks = [asyncio.create_task(client.get(url)) for url in urls]
+        for future in asyncio.as_completed(tasks):
+            try:
+                response = await future
+            except Exception as e:
+                yield ("", str(e), b"")
+                continue
+            url = str(response.url)
+            try:
+                response.raise_for_status()
+                yield (url, response.status_code, await response.aread())
+            except httpx.HTTPStatusError as e:
+                if e.response.status_code == 200:
+                    # This should not happen
+                    yield (url, str(e), b"")
+                else:
+                    yield (url, e.response.status_code, b"")
+            except Exception as e:
+                yield (url, str(e), b"")
+
+
 async def is_dir_resolve(path: pathlib.Path) -> pathlib.Path | None:
     try:
         resolved_path = await asyncio.to_thread(path.resolve)
@@ -433,6 +485,26 @@ def parse_key_blocks(keys_text: str) -> list[str]:
     return key_blocks
 
 
+def parse_key_blocks_bytes(keys_data: bytes) -> list[str]:
+    """Extract GPG key blocks from a KEYS file."""
+    key_blocks = []
+    current_block = []
+    in_key_block = False
+
+    for line in keys_data.splitlines():
+        if line.strip() == b"-----BEGIN PGP PUBLIC KEY BLOCK-----":
+            in_key_block = True
+            current_block = [line]
+        elif (line.strip() == b"-----END PGP PUBLIC KEY BLOCK-----") and 
in_key_block:
+            current_block.append(line)
+            key_blocks.append(b"\n".join(current_block))
+            in_key_block = False
+        elif in_key_block:
+            current_block.append(line)
+
+    return key_blocks
+
+
 async def paths_recursive(base_path: pathlib.Path) -> 
AsyncGenerator[pathlib.Path]:
     """Yield all file paths recursively within a base path, relative to the 
base path."""
     if (resolved_base_path := await is_dir_resolve(base_path)) is None:
diff --git a/poetry.lock b/poetry.lock
index 57f38af..f719cd0 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2302,6 +2302,140 @@ all = ["nodejs-wheel-binaries", "twine (>=3.4.1)"]
 dev = ["twine (>=3.4.1)"]
 nodejs = ["nodejs-wheel-binaries"]
 
+[[package]]
+name = "pysequoia"
+version = "0.1.29"
+description = "Provides OpenPGP facilities using Sequoia-PGP library"
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+files = [
+    {file = 
"pysequoia-0.1.29-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
 hash = 
"sha256:7f01e4d3110a4a53686f48e7d473c56e0d0c864d2eb5431eac1f8cd52e89f574"},
+    {file = 
"pysequoia-0.1.29-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", 
hash = 
"sha256:7c2eb0da2e190367b8f145397f1a1c8d1749e18b3d552876be4816557c3b5483"},
+    {file = 
"pysequoia-0.1.29-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",
 hash = 
"sha256:dc1223d7a43651c229cac7a912fbd255f628f6b31a2d692c0567644c69dd81c1"},
+    {file = 
"pysequoia-0.1.29-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", 
hash = 
"sha256:ce360808437982d98c549dd7c01570e525be0103988d37ca25b9b407322caa5d"},
+    {file = 
"pysequoia-0.1.29-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:085ddafa1aa269dc30e75110ee712a0e23165111480bb156894ad63a261e6628"},
+    {file = 
"pysequoia-0.1.29-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = 
"sha256:4e36e1800f3bcf446ba7262de19bacf21dd1ffc1785b3bf11313de65b10a35b0"},
+    {file = "pysequoia-0.1.29-cp310-cp310-musllinux_1_2_aarch64.whl", hash = 
"sha256:6fd15bfbe384fdafd95b73daf185a4346cd0b9b3764ff0840227d8872e5b538e"},
+    {file = "pysequoia-0.1.29-cp310-cp310-musllinux_1_2_armv7l.whl", hash = 
"sha256:d17e0d337428fe1ab4559e946d48163c831bd94fd0fd174aeee774959e47d1a3"},
+    {file = "pysequoia-0.1.29-cp310-cp310-musllinux_1_2_i686.whl", hash = 
"sha256:c00457c3664918b22b112f8e56bf2647dfee0aaefbebeda3c77734b1e7b44111"},
+    {file = "pysequoia-0.1.29-cp310-cp310-musllinux_1_2_x86_64.whl", hash = 
"sha256:b4138b987ce64474d3219e4f65794e22c1743be1705cd2b83f75a381eefe770d"},
+    {file = "pysequoia-0.1.29-cp310-cp310-win32.whl", hash = 
"sha256:87309b6f77a9b3b3b0a35a6dfbb2d928c067410cac2b785a05dbaf4e955eb790"},
+    {file = "pysequoia-0.1.29-cp310-cp310-win_amd64.whl", hash = 
"sha256:6e823616db7247c64a26983504479fea7508b5cc179664ca656d576201f8656e"},
+    {file = "pysequoia-0.1.29-cp311-cp311-macosx_10_12_x86_64.whl", hash = 
"sha256:42f8046e66049825b10cbe13da7546c94d7f63ea7623f874730b0fffc9b5e522"},
+    {file = "pysequoia-0.1.29-cp311-cp311-macosx_11_0_arm64.whl", hash = 
"sha256:91c3abf93beed35081d1ed61d74b5d3bf7eb247df34b4aa4bda989455f0df382"},
+    {file = 
"pysequoia-0.1.29-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
 hash = 
"sha256:1d759b373147a1642dcf99c48d11f14859acd0b458243e2833dbb0fa0b0cc5ec"},
+    {file = 
"pysequoia-0.1.29-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", 
hash = 
"sha256:dd8440ad35ccf1cc72e92c626062a6a3549f8d629e9d45b8eba70e8aac4edfda"},
+    {file = 
"pysequoia-0.1.29-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",
 hash = 
"sha256:440cbd4cdbd71eb7e687b273188e0661c914c5bce4c9bd79a6f0b116435fa4e7"},
+    {file = 
"pysequoia-0.1.29-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", 
hash = 
"sha256:3b20603358e1309a73b1b109198829465c88d64af7c2b4349ef38bc7bc230a26"},
+    {file = 
"pysequoia-0.1.29-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:46417d14d7e30cf9beb2a63391bdecb1389538a8c6803562f33c52248ea0df6c"},
+    {file = 
"pysequoia-0.1.29-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = 
"sha256:4fd1e9cd746162f4d765c404a55a8f1c8121c02319fdd73e10fc49c7b37893eb"},
+    {file = "pysequoia-0.1.29-cp311-cp311-musllinux_1_2_aarch64.whl", hash = 
"sha256:76ce026f03ccfd7057284550faab05a724d1557480d6850d9fcf6c798b810530"},
+    {file = "pysequoia-0.1.29-cp311-cp311-musllinux_1_2_armv7l.whl", hash = 
"sha256:c7f70c8cbb1ee94ca205dded1472596135dc1db78eeb0d29a9a9483ebab63c38"},
+    {file = "pysequoia-0.1.29-cp311-cp311-musllinux_1_2_i686.whl", hash = 
"sha256:44ec69ae02d77811085a3a2af990e5bf33611e9f65fc517fb2543ad8c2456b1f"},
+    {file = "pysequoia-0.1.29-cp311-cp311-musllinux_1_2_x86_64.whl", hash = 
"sha256:98e9a808c6a1dc8806c8b6ed32099e8cb95f09c27d0a9129e68759a2dd6a90a6"},
+    {file = "pysequoia-0.1.29-cp311-cp311-win32.whl", hash = 
"sha256:3a4a4ff85d720cb185f4af55c28bcefe6117b76b932db977fb6d611738c671e1"},
+    {file = "pysequoia-0.1.29-cp311-cp311-win_amd64.whl", hash = 
"sha256:22fcf180fa00a653ab2edeb5ef5f2e0e16881bb7d75faf7cc67963fb1b595bc3"},
+    {file = "pysequoia-0.1.29-cp312-cp312-macosx_10_12_x86_64.whl", hash = 
"sha256:fd535cd68cf38db10209dcc2ac6f02e6259e44160f0b55d03038d320102ab1eb"},
+    {file = "pysequoia-0.1.29-cp312-cp312-macosx_11_0_arm64.whl", hash = 
"sha256:67afdc183aab650529e54898782b758dae364b228dbe8a8663acd12e1754f3c8"},
+    {file = 
"pysequoia-0.1.29-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
 hash = 
"sha256:22757e27f0c24ec9dee0942c35f2205ed7aaf46ca76cc99872bb1dad6484d098"},
+    {file = 
"pysequoia-0.1.29-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", 
hash = 
"sha256:6f5d8b80ff695b4428652754a6f5a477ce87cddfc76541c47c4a2f8ec33c18b2"},
+    {file = 
"pysequoia-0.1.29-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",
 hash = 
"sha256:5f4cbbea46809fc4b50648ecce1e419c67bb9c03eed7a25afde803298ce633c6"},
+    {file = 
"pysequoia-0.1.29-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", 
hash = 
"sha256:4e180503ba06836ad3de1e0b5329f548f9ef77077ef63d5d6a02c4a1f2bab744"},
+    {file = 
"pysequoia-0.1.29-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:a50e8ed06a1c849104ad5ac8d5feb2e9d1a96d3d0b2370bccfcf4892375f3f23"},
+    {file = 
"pysequoia-0.1.29-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = 
"sha256:65f4b335950776aed6ca9c9509c074863ad83dcee5aa4c16cc51cfda37ce9860"},
+    {file = "pysequoia-0.1.29-cp312-cp312-musllinux_1_2_aarch64.whl", hash = 
"sha256:f989c65bed9d1bbbd324b1e8de23a74c99dc2f838144ed468e639785c63bf6ab"},
+    {file = "pysequoia-0.1.29-cp312-cp312-musllinux_1_2_armv7l.whl", hash = 
"sha256:60dcf853858b94ab468d10789100f9767d6b8ee24892ba14bdcf9a4725e88634"},
+    {file = "pysequoia-0.1.29-cp312-cp312-musllinux_1_2_i686.whl", hash = 
"sha256:f622053e787531e51c3295515e1c4dcd766318ed0479b7ae4b0c593566e298fa"},
+    {file = "pysequoia-0.1.29-cp312-cp312-musllinux_1_2_x86_64.whl", hash = 
"sha256:d42e5eab63357758da5661cb5f35ff78c1cddeb4a00b055ef74ca72e3e294c89"},
+    {file = "pysequoia-0.1.29-cp312-cp312-win32.whl", hash = 
"sha256:462486eb0dc68f8b0e385eac52623de35088a81c5998543f0db0f009e2e2493f"},
+    {file = "pysequoia-0.1.29-cp312-cp312-win_amd64.whl", hash = 
"sha256:f0c0f8fae955467a23fa3421c3d5694a7ed66f0d173a64000f181f87aad46c41"},
+    {file = "pysequoia-0.1.29-cp313-cp313-macosx_10_12_x86_64.whl", hash = 
"sha256:f9e422365d004a1cb74e95cce7c9f793781ce0833ac3f4e66133541901307151"},
+    {file = "pysequoia-0.1.29-cp313-cp313-macosx_11_0_arm64.whl", hash = 
"sha256:3b7d7a59ccf8f68fced8431ba73f5746faeb70cbd1634d660544f814ae12c9be"},
+    {file = 
"pysequoia-0.1.29-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
 hash = 
"sha256:58f91b08d3d8aeb8abd51e6048f3d0660fc99b2d0d1d1bb8eb74d44b9f788e25"},
+    {file = 
"pysequoia-0.1.29-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", 
hash = 
"sha256:24666d0752f0cf70ff0eaa38424413db0a587c5b77e1cf23e1b4064ec35d3632"},
+    {file = 
"pysequoia-0.1.29-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",
 hash = 
"sha256:4b1feabbd9177c82eb84f4f87d58eb2ea3a59fa02b50626337b170d2cbb7ae0c"},
+    {file = 
"pysequoia-0.1.29-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", 
hash = 
"sha256:06b35189c8819b3c432917ceaad7b1581074f3c8c71c0488288c7dabfadb69a2"},
+    {file = 
"pysequoia-0.1.29-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:ae898f13c6b754f5ad4c7fc158dd5bd40cb65a51ca22f93e9aeb84eb2faa429b"},
+    {file = 
"pysequoia-0.1.29-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = 
"sha256:6d8278141262b233ae91c7f50fb88d989830bf7ed4cfa7b4abdd04e1f1f1df7f"},
+    {file = "pysequoia-0.1.29-cp313-cp313-musllinux_1_2_aarch64.whl", hash = 
"sha256:9791b531f116e2fcb16e3f033be62700f336060f59e1b7c8efca6f097f33c1d4"},
+    {file = "pysequoia-0.1.29-cp313-cp313-musllinux_1_2_armv7l.whl", hash = 
"sha256:ff37f55f2acc1f4d3dd890ac4946e8cda29219fedaf6d02f9c3c6c12adf6be4c"},
+    {file = "pysequoia-0.1.29-cp313-cp313-musllinux_1_2_i686.whl", hash = 
"sha256:77e225e0a1c268a06f701fa88eed33d3cd5d225301ce04534e365605254aa61d"},
+    {file = "pysequoia-0.1.29-cp313-cp313-musllinux_1_2_x86_64.whl", hash = 
"sha256:886c692bb2b293a4a094a981eca73c22e7e2eb1da05a73b67e8571339dc6a36b"},
+    {file = "pysequoia-0.1.29-cp313-cp313-win32.whl", hash = 
"sha256:feb9fa7f7690f3b788ab74dade98dae5d9d94c4d14d32ba5cc80d25c27378a78"},
+    {file = "pysequoia-0.1.29-cp313-cp313-win_amd64.whl", hash = 
"sha256:0b68e1d66ca1af71ca11bf1ded8f76cab828584659d09883a4552411f6f63611"},
+    {file = 
"pysequoia-0.1.29-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
 hash = 
"sha256:978425a689f093e6f48303f56997576243036f4974a0edad91e146679a3986e9"},
+    {file = 
"pysequoia-0.1.29-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", 
hash = 
"sha256:e797bf9e47cb929ed487449877374a87807335a1728441f895d54277fccfd1cb"},
+    {file = 
"pysequoia-0.1.29-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",
 hash = 
"sha256:9d53365ead887504394ae988c7a51cf2772eaefcde913cc5efa0197ef0cf9bc9"},
+    {file = 
"pysequoia-0.1.29-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", 
hash = 
"sha256:3aea4e351d4451354e4a22f9deeb3fad6a9432b8c7e7a5eb0a7fff63ef19cc10"},
+    {file = "pysequoia-0.1.29-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = 
"sha256:ee1cd6300ff9cf063e4e3772bfe1e7b3934fdcc2b9c91bf32da02f43c2e30573"},
+    {file = "pysequoia-0.1.29-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = 
"sha256:f2826056511cf6e4e0f14b3b13410705a4eb2e878dc617cc4ba63909e7d718f9"},
+    {file = "pysequoia-0.1.29-cp313-cp313t-musllinux_1_2_i686.whl", hash = 
"sha256:c14bf7026e285948660c0cd68a644b43d1b5717b8e5c7b07d0fcb416c6375cb1"},
+    {file = "pysequoia-0.1.29-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = 
"sha256:749e0d11c83109c5e6ac85bb291d2bb5a6a81545eca73a31d01a85d8353a0cea"},
+    {file = 
"pysequoia-0.1.29-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:8a7489772acc70db9f4bea72e0796ff0706526e8851e5dae0f71b24dc49c27ed"},
+    {file = 
"pysequoia-0.1.29-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = 
"sha256:4502515826df73f30bc62c47e2100b80361b4ac32e69514bed051a7547c8dac3"},
+    {file = 
"pysequoia-0.1.29-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", 
hash = 
"sha256:0fb8796ee9ee05cd7673df5f2cbd2ab9af1bf176f86f654c6478019397711e9e"},
+    {file = 
"pysequoia-0.1.29-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", 
hash = 
"sha256:ef799eb16fde51ff9e7f2dcbcab9f45ba30a4fa9a3b33073776bb048430248eb"},
+    {file = 
"pysequoia-0.1.29-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", 
hash = 
"sha256:b91d5802b578d61b5afc259592ba527715568a1d52f040eb600f85564b1a0b4a"},
+    {file = 
"pysequoia-0.1.29-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", 
hash = 
"sha256:15b7f3dc196018df905eb47bb404306e1e461942b5d115b0d97f2dc5d912dec0"},
+    {file = "pysequoia-0.1.29-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = 
"sha256:eb21fad6adec34f24ed748b4da4d219cfefc77a2eb83f751b38da32393fd8ca4"},
+    {file = "pysequoia-0.1.29-cp37-cp37m-musllinux_1_2_armv7l.whl", hash = 
"sha256:944d87caa16455d2d4984d221d3aea640b542263dadefde0a0544d08b9256acc"},
+    {file = "pysequoia-0.1.29-cp37-cp37m-musllinux_1_2_i686.whl", hash = 
"sha256:d2899f81c2e3a6daeb6bbb9e3d1c3c841f40a881da5043aef9f997216d75113c"},
+    {file = "pysequoia-0.1.29-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = 
"sha256:07d5d6a427c496aa3032e50866d31ea7672e18c81d42f8134fb12bbf89cf3e77"},
+    {file = 
"pysequoia-0.1.29-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", 
hash = 
"sha256:b7e0271395c1164b941db155ab12b03100f779567a0c2d5f4ed8718542174681"},
+    {file = 
"pysequoia-0.1.29-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", 
hash = 
"sha256:7d8758569723daa08049f958b80251f01cfb33fd05458976915c190084e8b5ec"},
+    {file = 
"pysequoia-0.1.29-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", 
hash = 
"sha256:cbfecc9c910381cdbeab226687439d48af93f07860f65ab0d6232bafa515ef0b"},
+    {file = 
"pysequoia-0.1.29-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash 
= "sha256:b9bfb036a482ea012150bcbd5d77ecd058650c87dc4eb1532f6c9b822a6cae3b"},
+    {file = 
"pysequoia-0.1.29-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:753a4789aee59f4ff049c3ceb4e772ab4aa0176f9cb0d6eadbbc63e69900c8e9"},
+    {file = 
"pysequoia-0.1.29-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = 
"sha256:6736a3304d22e1e2e9f14c626a0adc79edddb76de252eb0b91feaf01c118449b"},
+    {file = "pysequoia-0.1.29-cp38-cp38-musllinux_1_2_aarch64.whl", hash = 
"sha256:2e176724e80483a131340d8a54fb057181568bebe3f51f94b2efa9eb318d35d3"},
+    {file = "pysequoia-0.1.29-cp38-cp38-musllinux_1_2_armv7l.whl", hash = 
"sha256:5a5126389e9eff69afb5aaf0105d17f5d05ad85bfb9d9242b3dd40bcc717ded7"},
+    {file = "pysequoia-0.1.29-cp38-cp38-musllinux_1_2_i686.whl", hash = 
"sha256:2f904f20faee6cb6eb2996d140a0ba42e957fb2cf28c5c5130a55cf22403dd07"},
+    {file = "pysequoia-0.1.29-cp38-cp38-musllinux_1_2_x86_64.whl", hash = 
"sha256:a569110ef0dc810cedb559f00c5b707918b7e33407599adc4904b70897358fa2"},
+    {file = "pysequoia-0.1.29-cp38-cp38-win32.whl", hash = 
"sha256:a778842fc0d9e13124dcd4dba427f8e8d0de9923824176d4f246595dae3e2238"},
+    {file = 
"pysequoia-0.1.29-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", 
hash = 
"sha256:b2ef102900801a2695a3e25cdc3f9e84e5e78eb7bcf9db00b9065657d14dba49"},
+    {file = 
"pysequoia-0.1.29-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", 
hash = 
"sha256:1b9cdacf7e3af31c217be78b309b235845a20f9fe00e4f79a1090141dd0d0719"},
+    {file = 
"pysequoia-0.1.29-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", 
hash = 
"sha256:f272babb43418872e48c4b5d1129cf19e5e443fd88b0809c180e47eaa3e7190c"},
+    {file = 
"pysequoia-0.1.29-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash 
= "sha256:3d29530a956fe33b20a7f7ffdb9f3d5a8933d98c634e5fb866b26e871165260c"},
+    {file = 
"pysequoia-0.1.29-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", 
hash = 
"sha256:36c0d321938372124e9b0bfe8e63ef623c1cd15703c3856b116fadcaff23fb8a"},
+    {file = 
"pysequoia-0.1.29-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = 
"sha256:1679898fb1f56d8b0419c59cee92ce3a53c61592d7f0113c95e9d86966148932"},
+    {file = "pysequoia-0.1.29-cp39-cp39-musllinux_1_2_aarch64.whl", hash = 
"sha256:cdbd7f92832cc4f75d442702c7178e5e27b96de77fefc3e870b314d3668ebb09"},
+    {file = "pysequoia-0.1.29-cp39-cp39-musllinux_1_2_armv7l.whl", hash = 
"sha256:f0d26ab62bcd1a00374ec8dbe8c393ed01ba930d8395722c17908e4521ef63fe"},
+    {file = "pysequoia-0.1.29-cp39-cp39-musllinux_1_2_i686.whl", hash = 
"sha256:3a90d71f7731d092ed0f60d22a2a01a24762850f10de7d5d3e9e6b602093a1ae"},
+    {file = "pysequoia-0.1.29-cp39-cp39-musllinux_1_2_x86_64.whl", hash = 
"sha256:13e994133bb8aa837cfa70b0b61339e5d8602a123534315dd292263838a685a4"},
+    {file = "pysequoia-0.1.29-cp39-cp39-win32.whl", hash = 
"sha256:b11f2a37c1d74b22123c9ce175cde37f2229250d8a9cac25e839cbee3125f22d"},
+    {file = "pysequoia-0.1.29-cp39-cp39-win_amd64.whl", hash = 
"sha256:4bf7d8731626074e71cbea6ee9132fbddcd2c9c982af59fa50738cf3eb154d56"},
+    {file = 
"pysequoia-0.1.29-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
 hash = 
"sha256:1207995ae91ed6440c0cfd3186b38262abaf4c78f4a90c45972e7462b47056dc"},
+    {file = 
"pysequoia-0.1.29-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",
 hash = 
"sha256:501e236b94d5a3a731dfc6c3db492b9d5456a62ad1e1322cbfa47b663b4a799f"},
+    {file = 
"pysequoia-0.1.29-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",
 hash = 
"sha256:4fbde22d58c682d4aa6f5b7d7176938f4465fcd268ed84766033d373d44de3c9"},
+    {file = 
"pysequoia-0.1.29-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl",
 hash = 
"sha256:77d554fe7af43f82d183971dcc0b90b809936a5ebf5174bcdac58bd869ae05a5"},
+    {file = 
"pysequoia-0.1.29-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
 hash = 
"sha256:2151dd7f3296e3c3e4f1c512bab0cd8fcf8b03b6453c878d635ff6f5b3c5dda3"},
+    {file = 
"pysequoia-0.1.29-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", 
hash = 
"sha256:aff6270affdcc0ff83aa880197782e38073726766de3670ccf02d693a13be447"},
+    {file = "pysequoia-0.1.29-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", 
hash = 
"sha256:3d4a32160c7ac321389afaf25775e3f34637ae8b1036bb2fc958dede90e65907"},
+    {file = "pysequoia-0.1.29-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", 
hash = 
"sha256:6c5bd1d5c1ee885de7351c88210a0856c0cc4554ff2706979b21baa680fe1d9f"},
+    {file = "pysequoia-0.1.29-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash 
= "sha256:f8c0fa3d458e3587da3f63ab5b457dd2d8aa9982ea93ce3622625bb8c5fac6b6"},
+    {file = "pysequoia-0.1.29-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", 
hash = 
"sha256:4b5d59b23e2be53fe0f6fd726a250cc6ce06ca5946f2f02f0f1843a01fa6ffcc"},
+    {file = 
"pysequoia-0.1.29-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
 hash = 
"sha256:002b98573627bc9f3c7758948476c76303f50cc1adf190df52b0788b68c2afdd"},
+    {file = 
"pysequoia-0.1.29-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",
 hash = 
"sha256:e8e422822c6662ba16c41f7ab9e3a61ac0484b2ee299b41f5efe0b93aaae52d3"},
+    {file = 
"pysequoia-0.1.29-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",
 hash = 
"sha256:577fad1f6d612552feb1380cf293203c04e153d8718944debdcec637f385802f"},
+    {file = 
"pysequoia-0.1.29-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl",
 hash = 
"sha256:a95080a37b447feea41068f2154d948a91ad4bc8d80617e0984ea2db647bf01b"},
+    {file = 
"pysequoia-0.1.29-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
 hash = 
"sha256:dd14391592e1e1db288cddf7c13b3a85f7f68c3605ca7968feb5fdbd88634fa9"},
+    {file = 
"pysequoia-0.1.29-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", 
hash = 
"sha256:b6be7819ade45a196b1531dd42db9115ab05114ae48bc0019663cc10b97499c2"},
+    {file = "pysequoia-0.1.29-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", 
hash = 
"sha256:06817388d55823cb9c4c9aabb7ebaa8c3d23316e4c97a6e346cfe843fc2a682a"},
+    {file = "pysequoia-0.1.29-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", 
hash = 
"sha256:21aaa5ead84916951d2df41d80807daecc08e49bd06aab1410de780d71b0c9d5"},
+    {file = "pysequoia-0.1.29-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash 
= "sha256:cf63ffc35ff7b88110e2b12aa5756a8e49734c842eed99f383533f48209589b0"},
+    {file = "pysequoia-0.1.29-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", 
hash = 
"sha256:4ffa1937ffe341b9b9a127b48bff98283a7dc7db6131c83c3deed3a78e8c180b"},
+    {file = 
"pysequoia-0.1.29-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
 hash = 
"sha256:13117d3863b853297fddc67e300908efe354321863787d3c43ca2c290fa95365"},
+    {file = 
"pysequoia-0.1.29-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",
 hash = 
"sha256:0602548135dfc8baa1756f21639845b8ce99092e37c8559ac2fffcd54dfc0b90"},
+    {file = 
"pysequoia-0.1.29-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",
 hash = 
"sha256:b6632f24abedcd9b2b8170667f698c63d428b1ffb9b417020c06ea9657bc1fb9"},
+    {file = 
"pysequoia-0.1.29-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl",
 hash = 
"sha256:8a14b2376cbca0db39211fe93fb935195315d6b491f9c988e1e442bd5c0d0a03"},
+    {file = "pysequoia-0.1.29-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", 
hash = 
"sha256:74821e397b2885833d6120c3cf544fc8aab6584f2eed62f7ad3770558d0c76c2"},
+    {file = "pysequoia-0.1.29-pp39-pypy39_pp73-musllinux_1_2_armv7l.whl", hash 
= "sha256:af9ec8f920f58d10afe72dd6e69e51b90ff8f0a42987279e4c41ac49934edfe1"},
+    {file = "pysequoia-0.1.29-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = 
"sha256:5bcb3d29982fec8a26c8026fbaef292eca7e6cf0b5889b2ad85a26b1f1fdd080"},
+    {file = "pysequoia-0.1.29-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash 
= "sha256:14b2529adda3275cdcf9fbb199938594517f1159985e272e67337ecc2b6890b3"},
+    {file = "pysequoia-0.1.29.tar.gz", hash = 
"sha256:3d6cd75c100aad565a16d64260ec8c1d7e43222724a8df5e47a0d8843341a09a"},
+]
+
 [[package]]
 name = "pytest"
 version = "8.4.0"
@@ -3178,4 +3312,4 @@ propcache = ">=0.2.1"
 [metadata]
 lock-version = "2.1"
 python-versions = "~=3.13"
-content-hash = 
"b79174b297f7612b074db005aa6399eace902e49302d56e0168540befd71a8f1"
+content-hash = 
"8dd458139228d185e9815f8ce7f69b683a64b3afc6a2c4801eacca61eeaa84bb"
diff --git a/pyproject.toml b/pyproject.toml
index 19f5ef1..bd9f7e9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -35,6 +35,7 @@ dependencies = [
   "quart-wtforms~=1.0.3",
   "rich~=14.0.0",
   "sqlmodel~=0.0.24",
+  "pysequoia (>=0.1.29,<0.2.0)",
 ]
 
 [dependency-groups]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to