mirror of
https://github.com/d3vyce/fastapi-toolsets.git
synced 2026-08-05 16:14:08 +00:00
Compare commits
3
Commits
d4ce652c1c
...
2a427a2946
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a427a2946 | ||
|
|
f57c9e40b9 | ||
|
|
7faf252c23 |
@@ -141,6 +141,37 @@ Use `first` when you only care about any one match and don't need uniqueness:
|
||||
user = await UserCrud.first(session=session, filters=[User.is_active == True])
|
||||
```
|
||||
|
||||
## Row locking
|
||||
|
||||
`get`, `get_or_none`, `first`, `get_multi`, and `update` all accept a `with_for_update` parameter that appends a `FOR UPDATE` clause to the underlying `SELECT`, preventing concurrent transactions from modifying the matched rows until the current transaction commits.
|
||||
|
||||
| Value | SQL clause |
|
||||
|---|---|
|
||||
| `False` (default) | no locking |
|
||||
| `True` | `FOR UPDATE` |
|
||||
| `"nowait"` | `FOR UPDATE NOWAIT` |
|
||||
| `"skip_locked"` | `FOR UPDATE SKIP LOCKED` |
|
||||
|
||||
```python
|
||||
# Lock before reading — typical read-modify-write pattern
|
||||
user = await UserCrud.get(session, [User.id == user_id], with_for_update=True)
|
||||
|
||||
# Raise immediately if another transaction holds the lock
|
||||
user = await UserCrud.get(session, [User.id == user_id], with_for_update="nowait")
|
||||
|
||||
# Skip rows already locked by another transaction (e.g. job queues)
|
||||
rows = await JobCrud.get_multi(session, filters=[Job.status == "pending"], with_for_update="skip_locked")
|
||||
|
||||
# Lock atomically as part of update (prevents race between SELECT and UPDATE)
|
||||
user = await UserCrud.update(session, UserUpdate(credits=10), [User.id == user_id], with_for_update=True)
|
||||
```
|
||||
|
||||
!!! warning
|
||||
`with_for_update` requires an open transaction. Wrap your call in `async with session.begin()` or use the `get_transaction` helper if you are not already inside one.
|
||||
|
||||
!!! note
|
||||
`NOWAIT` raises `sqlalchemy.exc.OperationalError` immediately if the row is locked rather than waiting.
|
||||
|
||||
## Pagination
|
||||
|
||||
!!! info "Added in `v1.1` (only offset_pagination via `paginate` if `<v1.1`)"
|
||||
|
||||
@@ -10,7 +10,7 @@ from collections.abc import Awaitable, Callable, Sequence
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from enum import Enum
|
||||
from typing import Any, ClassVar, Generic, Literal, Self, cast, overload
|
||||
from typing import Any, ClassVar, Generic, Literal, Self, TypeAlias, cast, overload
|
||||
|
||||
from fastapi import Query
|
||||
from pydantic import BaseModel
|
||||
@@ -52,6 +52,19 @@ from .search import (
|
||||
)
|
||||
|
||||
|
||||
_ForUpdateMode: TypeAlias = bool | Literal["nowait", "skip_locked"]
|
||||
|
||||
|
||||
def _apply_for_update(q: Any, mode: _ForUpdateMode) -> Any:
|
||||
if not mode:
|
||||
return q
|
||||
if mode == "nowait":
|
||||
return q.with_for_update(nowait=True)
|
||||
if mode == "skip_locked":
|
||||
return q.with_for_update(skip_locked=True)
|
||||
return q.with_for_update()
|
||||
|
||||
|
||||
class _CursorDirection(str, Enum):
|
||||
NEXT = "next"
|
||||
PREV = "prev"
|
||||
@@ -733,7 +746,7 @@ class AsyncCrud(Generic[ModelType]):
|
||||
*,
|
||||
joins: JoinType | None = None,
|
||||
outer_join: bool = False,
|
||||
with_for_update: bool = False,
|
||||
with_for_update: _ForUpdateMode = False,
|
||||
load_options: Sequence[ExecutableOption] | None = None,
|
||||
schema: type[SchemaType],
|
||||
) -> Response[SchemaType]: ...
|
||||
@@ -747,7 +760,7 @@ class AsyncCrud(Generic[ModelType]):
|
||||
*,
|
||||
joins: JoinType | None = None,
|
||||
outer_join: bool = False,
|
||||
with_for_update: bool = False,
|
||||
with_for_update: _ForUpdateMode = False,
|
||||
load_options: Sequence[ExecutableOption] | None = None,
|
||||
schema: None = ...,
|
||||
) -> ModelType: ...
|
||||
@@ -760,7 +773,7 @@ class AsyncCrud(Generic[ModelType]):
|
||||
*,
|
||||
joins: JoinType | None = None,
|
||||
outer_join: bool = False,
|
||||
with_for_update: bool = False,
|
||||
with_for_update: _ForUpdateMode = False,
|
||||
load_options: Sequence[ExecutableOption] | None = None,
|
||||
schema: type[BaseModel] | None = None,
|
||||
) -> ModelType | Response[Any]:
|
||||
@@ -805,7 +818,7 @@ class AsyncCrud(Generic[ModelType]):
|
||||
*,
|
||||
joins: JoinType | None = None,
|
||||
outer_join: bool = False,
|
||||
with_for_update: bool = False,
|
||||
with_for_update: _ForUpdateMode = False,
|
||||
load_options: Sequence[ExecutableOption] | None = None,
|
||||
schema: type[SchemaType],
|
||||
) -> Response[SchemaType] | None: ...
|
||||
@@ -819,7 +832,7 @@ class AsyncCrud(Generic[ModelType]):
|
||||
*,
|
||||
joins: JoinType | None = None,
|
||||
outer_join: bool = False,
|
||||
with_for_update: bool = False,
|
||||
with_for_update: _ForUpdateMode = False,
|
||||
load_options: Sequence[ExecutableOption] | None = None,
|
||||
schema: None = ...,
|
||||
) -> ModelType | None: ...
|
||||
@@ -832,7 +845,7 @@ class AsyncCrud(Generic[ModelType]):
|
||||
*,
|
||||
joins: JoinType | None = None,
|
||||
outer_join: bool = False,
|
||||
with_for_update: bool = False,
|
||||
with_for_update: _ForUpdateMode = False,
|
||||
load_options: Sequence[ExecutableOption] | None = None,
|
||||
schema: type[BaseModel] | None = None,
|
||||
) -> ModelType | Response[Any] | None:
|
||||
@@ -864,8 +877,7 @@ class AsyncCrud(Generic[ModelType]):
|
||||
q = q.where(and_(*filters))
|
||||
if resolved := cls._resolve_load_options(load_options):
|
||||
q = q.options(*resolved)
|
||||
if with_for_update:
|
||||
q = q.with_for_update()
|
||||
q = _apply_for_update(q, with_for_update)
|
||||
result = await session.execute(q)
|
||||
item = result.unique().scalar_one_or_none()
|
||||
if item is None:
|
||||
@@ -884,7 +896,7 @@ class AsyncCrud(Generic[ModelType]):
|
||||
*,
|
||||
joins: JoinType | None = None,
|
||||
outer_join: bool = False,
|
||||
with_for_update: bool = False,
|
||||
with_for_update: _ForUpdateMode = False,
|
||||
load_options: Sequence[ExecutableOption] | None = None,
|
||||
schema: type[SchemaType],
|
||||
) -> Response[SchemaType] | None: ...
|
||||
@@ -898,7 +910,7 @@ class AsyncCrud(Generic[ModelType]):
|
||||
*,
|
||||
joins: JoinType | None = None,
|
||||
outer_join: bool = False,
|
||||
with_for_update: bool = False,
|
||||
with_for_update: _ForUpdateMode = False,
|
||||
load_options: Sequence[ExecutableOption] | None = None,
|
||||
schema: None = ...,
|
||||
) -> ModelType | None: ...
|
||||
@@ -911,7 +923,7 @@ class AsyncCrud(Generic[ModelType]):
|
||||
*,
|
||||
joins: JoinType | None = None,
|
||||
outer_join: bool = False,
|
||||
with_for_update: bool = False,
|
||||
with_for_update: _ForUpdateMode = False,
|
||||
load_options: Sequence[ExecutableOption] | None = None,
|
||||
schema: type[BaseModel] | None = None,
|
||||
) -> ModelType | Response[Any] | None:
|
||||
@@ -937,8 +949,7 @@ class AsyncCrud(Generic[ModelType]):
|
||||
q = q.where(and_(*filters))
|
||||
if resolved := cls._resolve_load_options(load_options):
|
||||
q = q.options(*resolved)
|
||||
if with_for_update:
|
||||
q = q.with_for_update()
|
||||
q = _apply_for_update(q, with_for_update)
|
||||
result = await session.execute(q)
|
||||
item = result.unique().scalars().first()
|
||||
if item is None:
|
||||
@@ -956,6 +967,7 @@ class AsyncCrud(Generic[ModelType]):
|
||||
filters: list[Any] | None = None,
|
||||
joins: JoinType | None = None,
|
||||
outer_join: bool = False,
|
||||
with_for_update: _ForUpdateMode = False,
|
||||
load_options: Sequence[ExecutableOption] | None = None,
|
||||
order_by: OrderByClause | None = None,
|
||||
limit: int | None = None,
|
||||
@@ -968,6 +980,9 @@ class AsyncCrud(Generic[ModelType]):
|
||||
filters: List of SQLAlchemy filter conditions
|
||||
joins: List of (model, condition) tuples for joining related tables
|
||||
outer_join: Use LEFT OUTER JOIN instead of INNER JOIN
|
||||
with_for_update: Lock rows for update. ``True`` for plain ``FOR UPDATE``,
|
||||
``"nowait"`` for ``FOR UPDATE NOWAIT``, ``"skip_locked"`` for
|
||||
``FOR UPDATE SKIP LOCKED``.
|
||||
load_options: SQLAlchemy loader options
|
||||
order_by: Column or list of columns to order by
|
||||
limit: Max number of rows to return
|
||||
@@ -982,6 +997,7 @@ class AsyncCrud(Generic[ModelType]):
|
||||
q = q.where(and_(*filters))
|
||||
if resolved := cls._resolve_load_options(load_options):
|
||||
q = q.options(*resolved)
|
||||
q = _apply_for_update(q, with_for_update)
|
||||
if order_by is not None:
|
||||
q = q.order_by(order_by)
|
||||
if offset is not None:
|
||||
@@ -1001,6 +1017,7 @@ class AsyncCrud(Generic[ModelType]):
|
||||
*,
|
||||
exclude_unset: bool = True,
|
||||
exclude_none: bool = False,
|
||||
with_for_update: _ForUpdateMode = False,
|
||||
schema: type[SchemaType],
|
||||
) -> Response[SchemaType]: ...
|
||||
|
||||
@@ -1014,6 +1031,7 @@ class AsyncCrud(Generic[ModelType]):
|
||||
*,
|
||||
exclude_unset: bool = True,
|
||||
exclude_none: bool = False,
|
||||
with_for_update: _ForUpdateMode = False,
|
||||
schema: None = ...,
|
||||
) -> ModelType: ...
|
||||
|
||||
@@ -1026,6 +1044,7 @@ class AsyncCrud(Generic[ModelType]):
|
||||
*,
|
||||
exclude_unset: bool = True,
|
||||
exclude_none: bool = False,
|
||||
with_for_update: _ForUpdateMode = False,
|
||||
schema: type[BaseModel] | None = None,
|
||||
) -> ModelType | Response[Any]:
|
||||
"""Update a record in the database.
|
||||
@@ -1036,6 +1055,9 @@ class AsyncCrud(Generic[ModelType]):
|
||||
filters: List of SQLAlchemy filter conditions
|
||||
exclude_unset: Exclude fields not explicitly set in the schema
|
||||
exclude_none: Exclude fields with None value
|
||||
with_for_update: Lock the row before updating. ``True`` for plain
|
||||
``FOR UPDATE``, ``"nowait"`` for ``FOR UPDATE NOWAIT``,
|
||||
``"skip_locked"`` for ``FOR UPDATE SKIP LOCKED``.
|
||||
schema: Pydantic schema to serialize the result into. When provided,
|
||||
the result is automatically wrapped in a ``Response[schema]``.
|
||||
|
||||
@@ -1059,6 +1081,7 @@ class AsyncCrud(Generic[ModelType]):
|
||||
db_model = await cls.get(
|
||||
session=session,
|
||||
filters=filters,
|
||||
with_for_update=with_for_update,
|
||||
load_options=m2m_load_options or None,
|
||||
)
|
||||
values = obj.model_dump(
|
||||
|
||||
+203
-1
@@ -670,6 +670,28 @@ class TestCrudFirst:
|
||||
assert role is not None
|
||||
assert role.name == "admin"
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_first_with_for_update_nowait(self, db_session: AsyncSession):
|
||||
"""First with with_for_update='nowait' emits FOR UPDATE NOWAIT."""
|
||||
await RoleCrud.create(db_session, RoleCreate(name="nowait_first"))
|
||||
|
||||
role = await RoleCrud.first(
|
||||
db_session, [Role.name == "nowait_first"], with_for_update="nowait"
|
||||
)
|
||||
assert role is not None
|
||||
assert role.name == "nowait_first"
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_first_with_for_update_skip_locked(self, db_session: AsyncSession):
|
||||
"""First with with_for_update='skip_locked' emits FOR UPDATE SKIP LOCKED."""
|
||||
await RoleCrud.create(db_session, RoleCreate(name="skip_first"))
|
||||
|
||||
role = await RoleCrud.first(
|
||||
db_session, [Role.name == "skip_first"], with_for_update="skip_locked"
|
||||
)
|
||||
assert role is not None
|
||||
assert role.name == "skip_first"
|
||||
|
||||
|
||||
class TestCrudGetMulti:
|
||||
"""Tests for CRUD get_multi operations."""
|
||||
@@ -735,6 +757,45 @@ class TestCrudGetMulti:
|
||||
names = [r.name for r in roles]
|
||||
assert names == ["alpha", "bravo", "charlie"]
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_get_multi_with_for_update(self, db_session: AsyncSession):
|
||||
"""get_multi() with with_for_update=True locks the rows."""
|
||||
await RoleCrud.create(db_session, RoleCreate(name="lock1"))
|
||||
await RoleCrud.create(db_session, RoleCreate(name="lock2"))
|
||||
|
||||
roles = await RoleCrud.get_multi(
|
||||
db_session,
|
||||
filters=[Role.name.in_(["lock1", "lock2"])],
|
||||
with_for_update=True,
|
||||
)
|
||||
assert len(roles) == 2
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_get_multi_with_for_update_nowait(self, db_session: AsyncSession):
|
||||
"""get_multi() with with_for_update='nowait' emits FOR UPDATE NOWAIT."""
|
||||
await RoleCrud.create(db_session, RoleCreate(name="nowait_multi"))
|
||||
|
||||
roles = await RoleCrud.get_multi(
|
||||
db_session,
|
||||
filters=[Role.name == "nowait_multi"],
|
||||
with_for_update="nowait",
|
||||
)
|
||||
assert len(roles) == 1
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_get_multi_with_for_update_skip_locked(
|
||||
self, db_session: AsyncSession
|
||||
):
|
||||
"""get_multi() with with_for_update='skip_locked' emits FOR UPDATE SKIP LOCKED."""
|
||||
await RoleCrud.create(db_session, RoleCreate(name="skip_multi"))
|
||||
|
||||
roles = await RoleCrud.get_multi(
|
||||
db_session,
|
||||
filters=[Role.name == "skip_multi"],
|
||||
with_for_update="skip_locked",
|
||||
)
|
||||
assert len(roles) == 1
|
||||
|
||||
|
||||
class TestCrudUpdate:
|
||||
"""Tests for CRUD update operations."""
|
||||
@@ -781,6 +842,48 @@ class TestCrudUpdate:
|
||||
assert updated.email == "john@test.com"
|
||||
assert updated.is_active is True
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_update_with_for_update(self, db_session: AsyncSession):
|
||||
"""update() with with_for_update=True locks the row before writing."""
|
||||
role = await RoleCrud.create(db_session, RoleCreate(name="before"))
|
||||
|
||||
updated = await RoleCrud.update(
|
||||
db_session,
|
||||
RoleUpdate(name="after"),
|
||||
[Role.id == role.id],
|
||||
with_for_update=True,
|
||||
)
|
||||
|
||||
assert updated.name == "after"
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_update_with_for_update_nowait(self, db_session: AsyncSession):
|
||||
"""update() with with_for_update='nowait' locks the row with NOWAIT."""
|
||||
role = await RoleCrud.create(db_session, RoleCreate(name="before_nowait"))
|
||||
|
||||
updated = await RoleCrud.update(
|
||||
db_session,
|
||||
RoleUpdate(name="after_nowait"),
|
||||
[Role.id == role.id],
|
||||
with_for_update="nowait",
|
||||
)
|
||||
|
||||
assert updated.name == "after_nowait"
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_update_with_for_update_skip_locked(self, db_session: AsyncSession):
|
||||
"""update() with with_for_update='skip_locked' locks the row with SKIP LOCKED."""
|
||||
role = await RoleCrud.create(db_session, RoleCreate(name="before_skip"))
|
||||
|
||||
updated = await RoleCrud.update(
|
||||
db_session,
|
||||
RoleUpdate(name="after_skip"),
|
||||
[Role.id == role.id],
|
||||
with_for_update="skip_locked",
|
||||
)
|
||||
|
||||
assert updated.name == "after_skip"
|
||||
|
||||
|
||||
class TestCrudDelete:
|
||||
"""Tests for CRUD delete operations."""
|
||||
@@ -2610,7 +2713,7 @@ class TestCursorPaginateSearchJoins:
|
||||
|
||||
|
||||
class TestGetWithForUpdate:
|
||||
"""Tests for get() with with_for_update=True."""
|
||||
"""Tests for get/get_or_none with_for_update variants."""
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_get_with_for_update(self, db_session: AsyncSession):
|
||||
@@ -2626,6 +2729,105 @@ class TestGetWithForUpdate:
|
||||
assert result.id == role.id
|
||||
assert result.name == "locked"
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_get_with_for_update_nowait(self, db_session: AsyncSession):
|
||||
"""get() with with_for_update='nowait' emits FOR UPDATE NOWAIT."""
|
||||
role = await RoleCrud.create(db_session, RoleCreate(name="nowait"))
|
||||
|
||||
result = await RoleCrud.get(
|
||||
db_session,
|
||||
filters=[Role.id == role.id],
|
||||
with_for_update="nowait",
|
||||
)
|
||||
|
||||
assert result.id == role.id
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_get_with_for_update_skip_locked(self, db_session: AsyncSession):
|
||||
"""get() with with_for_update='skip_locked' emits FOR UPDATE SKIP LOCKED."""
|
||||
role = await RoleCrud.create(db_session, RoleCreate(name="skip"))
|
||||
|
||||
result = await RoleCrud.get(
|
||||
db_session,
|
||||
filters=[Role.id == role.id],
|
||||
with_for_update="skip_locked",
|
||||
)
|
||||
|
||||
assert result.id == role.id
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_get_or_none_with_for_update(self, db_session: AsyncSession):
|
||||
"""get_or_none() with with_for_update=True locks the row."""
|
||||
role = await RoleCrud.create(db_session, RoleCreate(name="locked2"))
|
||||
|
||||
result = await RoleCrud.get_or_none(
|
||||
db_session,
|
||||
[Role.id == role.id],
|
||||
with_for_update=True,
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result.id == role.id
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_get_or_none_with_for_update_nowait(self, db_session: AsyncSession):
|
||||
"""get_or_none() with with_for_update='nowait' emits FOR UPDATE NOWAIT."""
|
||||
role = await RoleCrud.create(db_session, RoleCreate(name="nowait2"))
|
||||
|
||||
result = await RoleCrud.get_or_none(
|
||||
db_session,
|
||||
[Role.id == role.id],
|
||||
with_for_update="nowait",
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result.id == role.id
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_get_or_none_with_for_update_skip_locked(
|
||||
self, db_session: AsyncSession
|
||||
):
|
||||
"""get_or_none() with with_for_update='skip_locked' emits FOR UPDATE SKIP LOCKED."""
|
||||
role = await RoleCrud.create(db_session, RoleCreate(name="skip2"))
|
||||
|
||||
result = await RoleCrud.get_or_none(
|
||||
db_session,
|
||||
[Role.id == role.id],
|
||||
with_for_update="skip_locked",
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result.id == role.id
|
||||
|
||||
def test_for_update_sql_clauses(self):
|
||||
"""Verify _apply_for_update emits the correct SQL FOR UPDATE clauses."""
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
from fastapi_toolsets.crud.factory import _apply_for_update
|
||||
|
||||
base = select(Role)
|
||||
|
||||
plain = str(_apply_for_update(base, True).compile(dialect=postgresql.dialect()))
|
||||
assert "FOR UPDATE" in plain
|
||||
assert "NOWAIT" not in plain
|
||||
assert "SKIP LOCKED" not in plain
|
||||
|
||||
nowait = str(
|
||||
_apply_for_update(base, "nowait").compile(dialect=postgresql.dialect())
|
||||
)
|
||||
assert "FOR UPDATE NOWAIT" in nowait
|
||||
|
||||
skip = str(
|
||||
_apply_for_update(base, "skip_locked").compile(dialect=postgresql.dialect())
|
||||
)
|
||||
assert "FOR UPDATE SKIP LOCKED" in skip
|
||||
|
||||
no_lock = str(
|
||||
_apply_for_update(base, False).compile(dialect=postgresql.dialect())
|
||||
)
|
||||
assert "FOR UPDATE" not in no_lock
|
||||
|
||||
|
||||
class TestCursorPaginateColumnTypes:
|
||||
"""Tests for cursor_paginate() covering DateTime, Date and Numeric column types."""
|
||||
|
||||
@@ -1329,26 +1329,27 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ty"
|
||||
version = "0.0.33"
|
||||
version = "0.0.35"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/84/44/9478c50c266826c1bf30d1692e589755bffa8f1c0a3eb7af8a346c255991/ty-0.0.33.tar.gz", hash = "sha256:46d63bda07403322cb6c28ccfdd5536be916e13df725c29f7ccd0a21f06bd9e8", size = 5559373, upload-time = "2026-04-28T10:45:13.18Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4e/53/440e7b1212c4b0abbd4adb7aed93f4971aa1f8dca386ac5515930afa9172/ty-0.0.35.tar.gz", hash = "sha256:8375c240ab38138a19db07996c9808fb7a92047c1492e1ce587c2ef5112ad3a9", size = 5629237, upload-time = "2026-05-10T18:25:17.105Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/24/e287388c63a19191be26b32ff4dbd06029834068150ebe2532939bc4c851/ty-0.0.33-py3-none-linux_armv6l.whl", hash = "sha256:94d0a9d2234261a8911396d59e506b5923fe0971dbda43b9dcea287936887fcc", size = 11021308, upload-time = "2026-04-28T10:45:43.34Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/ca/ba1eed819895bd239fba8ee35dfcd5fcb266c203b0914a17a59579096bb5/ty-0.0.33-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4a2b5ba078f90de342f56b5f7979bb77c9b9b1d8625a041352ffc6ee93c4073", size = 10777272, upload-time = "2026-04-28T10:45:32.905Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/a8/c3131d37b44b3fea1d6654a1c929a0cd0873822f77a90482b8ec28f6fbbd/ty-0.0.33-py3-none-macosx_11_0_arm64.whl", hash = "sha256:84ff5707825e9af9668d2bcf66975f93e520a63b524ab494e3a8265735be2563", size = 10201078, upload-time = "2026-04-28T10:45:23.374Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/db/d8e37ff0045810cc65e1ff36aa0da0a2253c05659787ac987df8a16c7897/ty-0.0.33-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e375285736f57886868e7af0b11c7b0ec5b6543fa15e7ad2a714fed9f077d4e0", size = 10732347, upload-time = "2026-04-28T10:45:21.444Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/1a/20e83a412506a918e4684fc67b567cf7cc13b105470b3428cb23c3d5aa13/ty-0.0.33-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5680f6350c3b4e46b8bff6d7bb132366ea239463d6cad4892725d06046e65464", size = 10808238, upload-time = "2026-04-28T10:45:38.565Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/4b/d0a39f4464dc6cb4cc2c159473ce216bd1846bfb684c0323a3cb36dce5c6/ty-0.0.33-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5535538bad8d0f7e62bcdff02197cdb30e41451d80b35d27e17d128f2e1dc5d", size = 11288348, upload-time = "2026-04-28T10:45:08.419Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/7e/f1745e0f9583363d7a83d9a4990fc244f76ecc30840ddad83dc16a33c52d/ty-0.0.33-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:da196c42bbbc069e1e21e3e52107c061aa9660352dae57a41930690b56e2c02d", size = 11789907, upload-time = "2026-04-28T10:45:19.064Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/71/25f39f46a12d662859d45bc648555d0661044eb43db6b5648c9947487da9/ty-0.0.33-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9281672921ef6d4460e03146b5e6c18cb1a3e3a3b8a1a88f6f33226d05a469b7", size = 11500774, upload-time = "2026-04-28T10:45:48.012Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/ec/136959ecbb7c71cb90537f5aea441c73f4ab24612868a6ecdc9d7444d32d/ty-0.0.33-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82c1b8f303f82da64e878108e764be3ecbcd7c9903ac0a7f7031614ed00b97ab", size = 11360314, upload-time = "2026-04-28T10:45:05.402Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/95/32809575c222f00beed498cb728e9290a0f5009f930025381bb7253b2206/ty-0.0.33-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:efe3af412c9ff67bce5fa37d0a2b0d8555c24072b145a5bac6c79637f1c83abe", size = 10707785, upload-time = "2026-04-28T10:45:10.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/89/c8e9531f7aa4a093359e15fa32c8e1277fbbe90d16894d7c6032d29f4b34/ty-0.0.33-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:aeec29c91ea768601747da546c3efc20b72c2fb1bd52bcc786a5c6eeff51d27b", size = 10834987, upload-time = "2026-04-28T10:45:40.738Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/16/9835fbcf5338af1a1917bd28fdb8a7193c210b83f243aa286fa9f79cb3ad/ty-0.0.33-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a535977c52bbb5f7e96b8b70a6ad375ad077f4a9ff2492508ea3816a2b403819", size = 10968968, upload-time = "2026-04-28T10:45:30.26Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/69/64c76aabc1bc70c7f24b686cd93c3407f8ea430905e395f59bf9603ef571/ty-0.0.33-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1d732facf39fcb221ba279d469c5040d37883e964f123b1563888efd34818180", size = 11458077, upload-time = "2026-04-28T10:45:45.971Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/84/fae27b0c4718776a298690d31ca4cc1995f2e3e1c63a7b59e84c41498e9a/ty-0.0.33-py3-none-win32.whl", hash = "sha256:d90960b574428dc252f85e8598ec5fcb7f619794196b2fc95a90da075ed4681c", size = 10345364, upload-time = "2026-04-28T10:45:16.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/a0/a2938b23ae3e1a09a2d7c189e2ac5f7113676bae4e0e23948b568e18e5f8/ty-0.0.33-py3-none-win_amd64.whl", hash = "sha256:c1c3aec62c44de610c6e95f0a4e97ac3dbc07934bfdbf1fd90d758c9ff72f48e", size = 11342470, upload-time = "2026-04-28T10:45:26.455Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/62/7fb948aace38d2f6329261bb33c035a8484549c74f1db28649c7a4c6fed9/ty-0.0.33-py3-none-win_arm64.whl", hash = "sha256:0d44f99ba1b441e55e2aa301b2ac0a21112784931b46a5f66f4ea9efe5620d97", size = 10742673, upload-time = "2026-04-28T10:45:35.555Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/84/19662ee881675815b7fafff940a365be1985730465afd9b75cb2edd5f8b3/ty-0.0.35-py3-none-linux_armv6l.whl", hash = "sha256:85ae1e59b9fb0b40e9d84fe61b29653c5f2f5e78b487ece371a7a38c20c781cf", size = 11198741, upload-time = "2026-05-10T18:24:49.378Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/df/7e5b6f83d85b4d2e5b72b5dceb388f440acc10679417bd46f829b9200fab/ty-0.0.35-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:709dbb7af4fcadb1196863c00b8791bbbbcc9dacbe15a0ff17f0af82b35d415b", size = 10948304, upload-time = "2026-05-10T18:24:58.246Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/94/72d7263aca055cde427f0ebcf08d6a74e5a5fee1d1e7fdd553696089cecb/ty-0.0.35-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2cb0877419ab0c8708b6925cb0c2800b263842bd3c425113f200538772f3a0cc", size = 10407413, upload-time = "2026-05-10T18:24:37.422Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/23/fda6fae8a81ce0cb5f24cdfe63260e110c7af8844e31fa07d1e6e8ef0232/ty-0.0.35-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7afbcfc61904b7e82e7fe1a1db832a40d8f01e69dee1775f6594e552980536c", size = 10932614, upload-time = "2026-05-10T18:24:47.401Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/3d/b98d8d4aa1a5ed6daaf15864e838f605ca7b1e8b93b7e17b96ed4bc4dfed/ty-0.0.35-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b61498cc3e4178031c079951257fbdb209a891b4feb10ad6c40f615a51846f41", size = 10962982, upload-time = "2026-05-10T18:24:44.88Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/c4/2881aad71bf6fb2f8df17fc8e4bc89e904e54490a3ee747b5ef73f98ac85/ty-0.0.35-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:573b1eacda349fc8dba0d767b41631c3a6f66412363127c5bf2b1b40a1d898d2", size = 11476274, upload-time = "2026-05-10T18:24:42.4Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/0f/7717650adaeaddd23eea70470e2c26d3f0b9b18fdc7f26ec9552d6001f17/ty-0.0.35-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7209746158d6393c1040aa64b3ca29622e212ea7d8bae22ba50dbcbb4f96f0a", size = 12012027, upload-time = "2026-05-10T18:25:00.752Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/c9/1a16cb4aab6f4707d8f550772e91abc26d1c8870f19b5e2453ad10bb8209/ty-0.0.35-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4466a1470aa4418d49a9aa45d9da7de42033addd0a2837c5b2b0eb71d3c2bcd3", size = 11648894, upload-time = "2026-05-10T18:25:12.44Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/a1/a977c0e07e9f88db9c67f90c6342a4dc4422c8091fa07bf26521870687c5/ty-0.0.35-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb44bb742d52c309dcaa6598bcf4d82eb4bf1241b9e4940461e522e30093fe8b", size = 11560482, upload-time = "2026-05-10T18:25:05.172Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/c1/a5fb11227d5cc4ac3f29a115d8c8bc817578e8ef6907d1e4c914ddbf45ee/ty-0.0.35-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:34b219250736c989b2670a03782c61315f523f3a2be37f1f90b1207e2212c188", size = 11718495, upload-time = "2026-05-10T18:24:54.12Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/cb/e92e4317388b6d1fd821a46941b448a8a1ff0bf13e22147c5167d8fa1b00/ty-0.0.35-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:88e2ac497decc0940ef1a07571dee8a746112a93a09cdc7f8bca0099752e2e05", size = 10900815, upload-time = "2026-05-10T18:25:02.941Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/4f/03bd87388a92567f262f35ac64e10d2be047d258f2dfcf1405f500fa2b90/ty-0.0.35-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:02cae51b53e6ec17d5d827ff1a3a76fd119705b56a92156e04399eda6e911596", size = 10998051, upload-time = "2026-05-10T18:25:14.68Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/60/6edbc375ee6073973200096168f644e1081e5e55a7d42596826465b275de/ty-0.0.35-py3-none-musllinux_1_2_i686.whl", hash = "sha256:11871d730c9400d899ac0b9f3d660ed2e7e433377c8725549f8250a36a7f2620", size = 11148910, upload-time = "2026-05-10T18:24:51.842Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/b1/a845d2066ed521c477450f436d4bd353d107e7c02dd6536a485944aaf892/ty-0.0.35-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ad0a2f0530d0933dcc99ad36ac556c63e384ea72ab9a18d23ad2e2c9fd61c73", size = 11671005, upload-time = "2026-05-10T18:24:56.223Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/81/1d5912a54fb66b2f95ac828ae61d422ef5afeae1263e4d231e40796c229f/ty-0.0.35-py3-none-win32.whl", hash = "sha256:0e25d63ec4ab116e7f6757e44d16ca9216bca679d19ecc36d119cf80faada61a", size = 10481096, upload-time = "2026-05-10T18:24:39.976Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/36/1c7f8632bfec1c321f01581d4c940a3617b24bd3e8b37c8a7363d33fbfc4/ty-0.0.35-py3-none-win_amd64.whl", hash = "sha256:6a0a6d259f6f2f8f2f954c6f013d4e0b5eba68af6b353bf19a47d59ec254a3d5", size = 11555691, upload-time = "2026-05-10T18:25:07.792Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/fb/59325221bce52f6e833d6865ce8360ef7d5e1e21151b38df6dc77c4327a7/ty-0.0.35-py3-none-win_arm64.whl", hash = "sha256:619c52c0fb2aa21961a848a1995135ad3b6d0a9aa54da0194e60f679cc200e13", size = 10925457, upload-time = "2026-05-10T18:25:10.352Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1425,7 +1426,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "zensical"
|
||||
version = "0.0.40"
|
||||
version = "0.0.41"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
@@ -1437,18 +1438,18 @@ dependencies = [
|
||||
{ name = "pyyaml" },
|
||||
{ name = "tomli" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ba/a6/88062f7e235f58a5f05d82005fc35d9dbaed27c024fe9ffae5bce7f33661/zensical-0.0.40.tar.gz", hash = "sha256:5c294751977a664614cb84e987186ad8e282af77ce0d0d800fe48ee57791279d", size = 3920555, upload-time = "2026-05-04T16:19:07.962Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/89/d6/b3e931233e53a2377ef5915cc6e786845c3263306874a469af8fb569ef9c/zensical-0.0.41.tar.gz", hash = "sha256:6c3c90301123749dfc26a210d6c080f0691253c7c765ad308a10b4518369a6fe", size = 3927788, upload-time = "2026-05-09T14:35:29.005Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/c4/3066f4442923ca1e49269147b70ca7c84467524e8f5228724693b9ac85c2/zensical-0.0.40-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b65a7143c9c6a460880bf3e65b777952bd2dcede9dd17a6c6bac9b4a0686ad9b", size = 12691533, upload-time = "2026-05-04T16:18:31.72Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/cb/03e961cbd01620ea91aeb835b0b4e8848c7bcdf5a799a620fb3e57bfc277/zensical-0.0.40-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:045bdcb6d00a11ddcab7d379d0d986cdf78dba8e9287d8e628ef11958241507d", size = 12556486, upload-time = "2026-05-04T16:18:35.278Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/76/7dde50220808bdc5f5e63b97866a684418410b3cae9d00cdae1d449bcc20/zensical-0.0.40-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d48ec476c2e8ce3f8585a1278083aabc35ec80361f2c4fc4a53b9a525778f7fc", size = 12935602, upload-time = "2026-05-04T16:18:38.308Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/55/6c8ef951c390b42249738f4338498e7a1fd64ff09e44d7cc19f5c948c45b/zensical-0.0.40-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:48c38e0ae314c25f2e5e64210bbad9be6e970f2d40fe9da106586ad90ce5e85e", size = 12904314, upload-time = "2026-05-04T16:18:41.007Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/ae/95008f5dc2ee441efcdc2fab36ff29ce24d7477e53390fc340c8add39342/zensical-0.0.40-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f25f62dcd61f6306cab890dfa34c81d2709f5db290b4c3f2675343771db28c90", size = 13269946, upload-time = "2026-05-04T16:18:44.387Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/96/cdbb2bf04255ccaaa07861bdda1ee8dd1630d2233fc2f09636abbd5e084c/zensical-0.0.40-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:168fe3489dd93ae92978b4db11d9300c63e10d382b81634232c2872ce9e746c2", size = 12974962, upload-time = "2026-05-04T16:18:47.462Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/ce/66e86f89fc15bbe667794ba67d7efc8fa72fe7a1be19e1efb4246ff55442/zensical-0.0.40-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8652ba203bd588ebf2d66bda4457a4a7d8e193c886960859c75081c0e3b946de", size = 13111599, upload-time = "2026-05-04T16:18:50.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/76/3d71ebdabb02d79a5c523b5e646141c362c9559947078c8d56a9f3bd7a30/zensical-0.0.40-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:9ffa6cf208b7ab6b771703be827d4d8c7f07f173abeffb35a8015a0b832b2a40", size = 13175406, upload-time = "2026-05-04T16:18:53.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/6a/2bb5f730786d590f02cb0fef796c148d5ac0d5c1556f2d78c987ad4e1346/zensical-0.0.40-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:7101ba0c739c78bc3a57d22130b59b9e6fdf96c21c8a6b4244070de6b34527d4", size = 13324783, upload-time = "2026-05-04T16:18:56.41Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/8c/1d2ba1454360ee948dd0f0807b048c076d9578d0d9ebba2a438ecfa9f82f/zensical-0.0.40-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:39bf728a68a5418feeda8f3385cd1063fdb8d896a6812c3dede4267b2868df12", size = 13260045, upload-time = "2026-05-04T16:18:59.244Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/61/efd51c5c5e15cfd5498d59df250f60294cc44d36d8ce4dc2a76fa3669c2f/zensical-0.0.40-cp310-abi3-win32.whl", hash = "sha256:bc750c3ba8d11833d9b9ac8fc14adc3435225b6d17314a21a91eb60209511ca5", size = 12244913, upload-time = "2026-05-04T16:19:02.219Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/9e/f3f2118fbcfd1c2dc705491c8864c596b1a748b67ffe2a024e512b9201ab/zensical-0.0.40-cp310-abi3-win_amd64.whl", hash = "sha256:c5c86ac468df2dfe515ff54ffa97725c38226f1e5c970059b7e88078abab89ab", size = 12475762, upload-time = "2026-05-04T16:19:05.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/08/ee18207c9b4e3ada74a0f4adf253bea90da39ae43772761cd91072e3a1fc/zensical-0.0.41-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f06a0015dcfdf7aeca73f4998a401db65db0ae2dd72da9629a7be8f9a4d0b7b6", size = 12701539, upload-time = "2026-05-09T14:34:48.6Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/93/d4635fbbce8171cf71dd64285d9f6d5773a2b624b928f1dd8acaf1ee9f9f/zensical-0.0.41-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:4e524ce68c9ff082ffaded9f742407097cf51bab692b7bc18d3c174b966174fe", size = 12560038, upload-time = "2026-05-09T14:34:51.666Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/4a/1730a30377bbb0914ed740e0e289d379b0552673b6cf912aefe7a205440c/zensical-0.0.41-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4afe35331cd2394c408cd362458936479cc0ed4fb272478498e4794aafc7414", size = 12942926, upload-time = "2026-05-09T14:34:54.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/e3/d9a0416ef4edc043ce9f404a66f1934f102bcb645b103abb26b180ba5680/zensical-0.0.41-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15a850285050f03aeb3b67ce7d99943093059fe8d32fc7731fa9f27be45c64cc", size = 12912711, upload-time = "2026-05-09T14:34:57.174Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/d0/775852783bef835425306a2fcd8236ef14fd19160e1b4261e192bf2d9f54/zensical-0.0.41-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35052e9dbefabe3a71c4836cfc4afa6c9469e5eeddc2a3ee750803ae3fe777dc", size = 13275869, upload-time = "2026-05-09T14:34:59.93Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/95/554273cc09a270ced0213d3e0aac8b3fc2b472fc2b26771d56fc8fd55047/zensical-0.0.41-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47f459205fb55f64dcb6c65e9f3c2fa00a2b4306c5ef1b71b9a50c45007071d", size = 12980177, upload-time = "2026-05-09T14:35:02.81Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/b5/d74d5040b3121db5c72b0134f0455641b90b1277fb1330a8e5e0029ca8d3/zensical-0.0.41-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:aa3b3b3a4e6f75f6bb3c1aca1fad7a96cebf54cbd4e31122f6876503b8801666", size = 13119629, upload-time = "2026-05-09T14:35:07.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/9a/93527acd7750092d7fca2e6c43fe2b8f1e85e1c96a1002baf6a08201c6f7/zensical-0.0.41-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:565133fd48b2ce939698c174c0c1c6470407a8fb6a90a2bb0eeec97cd4344444", size = 13182183, upload-time = "2026-05-09T14:35:10.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/7e/d77e4c809bfcbad40db85a6a7beeda2ee5c964232e0186783c3a837a7d0b/zensical-0.0.41-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:cec0a2b05eaaace0c7424bab3f2884da03ade212cac4ba4487c58691ec13ec65", size = 13330444, upload-time = "2026-05-09T14:35:13.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/e8/ecbb7e34bff88aa892c676b8b2e2ddf425f94d66cbb84b80016095191b77/zensical-0.0.41-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1736f0cb7686628cc6f53952d208423f20b542f0c16b0c2ddd7e702bf6e41fdd", size = 13263093, upload-time = "2026-05-09T14:35:20.826Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/6f/48b2f81ce708d19bb807d94716f2772ec4b74389b6d29024669fc470df08/zensical-0.0.41-cp310-abi3-win32.whl", hash = "sha256:34a78645c68fba152faacb66516c895283166154f8b15b61440a6c21c84f0974", size = 12253644, upload-time = "2026-05-09T14:35:23.598Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/92/5cf943133f61b996965743deeaff467f278135521f58d83ca68d2601ded3/zensical-0.0.41-cp310-abi3-win_amd64.whl", hash = "sha256:00d80cd573152e0efb655143bbdfe8788eb4b33167a802639fdb1b1800b724ac", size = 12483190, upload-time = "2026-05-09T14:35:26.43Z" },
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user