# etcd-client-py
[](https://pypi.org/project/etcd-client-py/)

Python wrapper of [etcd_client](https://github.com/etcdv3/etcd-client) built with [PyO3](https://github.com/PyO3/pyo3).
## Installation
```bash
pip install etcd_client
```
## Basic usage
```python
from etcd_client import EtcdClient
etcd = EtcdClient(['http:://127.0.0.1:2379'])
```
Actual connection establishment with Etcd's gRPC channel will be done when you call `EtcdClient.connect()`.
```python
async def main():
async with etcd.connect() as communicator:
await communicator.put('testkey'.encode(), 'testvalue'.encode())
value = await communicator.get('testkey'.encode())
print(bytes(value).decode()) # testvalue
```
`EtcdCommunicator.get_prefix(prefix)` will return a tuple of list containing all key-values with given key prefix.
```python
async def main():
async with etcd.connect() as communicator:
await communicator.put('/testdir'.encode(), 'root'.encode())
await communicator.put('/testdir/1'.encode(), '1'.encode())
await communicator.put('/testdir/2'.encode(), '2'.encode())
await communicator.put('/testdir/2/3'.encode(), '3'.encode())
test_dir = await communicator.get_prefix('/testdir'.encode())
for resp in test_dir:
# ['/testdir', 'root']
# ['/testdir/1', '1']
# ['/testdir/2', '2']
# ['/testdir/2/3', '3']
print([bytes(v).decode() for v in resp])
```
## Operating with Etcd lock
Just like `EtcdClient.connect()`, you can easilly use etcd lock by calling `EtcdClient.with_lock(lock_opts)`.
```python
async def first():
async with etcd.with_lock(
EtcdLockOption(
lock_name='foolock'.encode(),
)
) as communicator:
value = await communicator.get('testkey'.encode())
print('first:', bytes(value).decode(), end=' | ')
async def second():
await asyncio.sleep(0.1)
async with etcd.with_lock(
EtcdLockOption(
lock_name='foolock'.encode(),
)
) as communicator:
value = await communicator.get('testkey'.encode())
print('second:', bytes(value).decode())
async with etcd.connect() as communicator:
await communicator.put('testkey'.encode(), 'testvalue'.encode())
await asyncio.gather(first(), second()) # first: testvalue | second: testvalue
```
Adding `timeout` parameter to `EtcdClient.with_lock()` call will add a timeout to lock acquiring process.
```python
async def first():
async with etcd.with_lock(
EtcdLockOption(
lock_name='foolock'.encode(),
)
) as communicator:
value = await communicator.get('testkey'.encode())
print('first:', bytes(value).decode(), end=' | ')
async def second():
await asyncio.sleep(0.1)
async with etcd.with_lock(
EtcdLockOption(
lock_name='foolock'.encode(),
timeout=5.0,
)
) as communicator:
value = await communicator.get('testkey'.encode())
print('second:', bytes(value).decode())
async with etcd.connect() as communicator:
await communicator.put('testkey'.encode(), 'testvalue'.encode())
await asyncio.gather(first(), second()) # first: testvalue | second: testvalue
```
Adding `ttl` parameter to `EtcdClient.with_lock()` call will force lock to be released after given seconds.
```python
async def first():
async with etcd.with_lock(
EtcdLockOption(lock_name="foolock".encode(), ttl=5)
) as communicator:
await asyncio.sleep(10)
async def second():
start = time.time()
async with etcd.with_lock(
EtcdLockOption(lock_name="foolock".encode(), ttl=5)
) as communicator:
print(f"acquired lock after {time.time() - start} seconds")
# 'second' acquired lock after 5.247947931289673 seconds
done, _ = await asyncio.wait([
asyncio.create_task(first()),
asyncio.create_task(second())
], return_when=asyncio.FIRST_COMPLETED)
for task in done:
print(task.result())
```
## Watch
You can watch changes on key with `EtcdCommunicator.watch(key)`.
```python
async def watch():
async with etcd.connect() as communicator:
async for event in communicator.watch('testkey'.encode()):
print(event.event, bytes(event.value).decode())
async def update():
await asyncio.sleep(0.1)
async with etcd.connect() as communicator:
await communicator.put('testkey'.encode(), '1'.encode())
await communicator.put('testkey'.encode(), '2'.encode())
await communicator.put('testkey'.encode(), '3'.encode())
await communicator.put('testkey'.encode(), '4'.encode())
await communicator.put('testkey'.encode(), '5'.encode())
await asyncio.gather(watch(), update())
# WatchEventType.PUT 1
# WatchEventType.PUT 2
# WatchEventType.PUT 3
# WatchEventType.PUT 4
# WatchEventType.PUT 5
```
Watching changes on keys with specific prefix can be also done by `EtcdCommunicator.watch_prefix(key_prefix)`.
```python
async def watch():
async with etcd.connect() as communicator:
async for event in communicator.watch_prefix('/testdir'.encode()):
print(event.event, bytes(event.key).decode(), bytes(event.value).decode())
async def update():
await asyncio.sleep(0.1)
async with etcd.connect() as communicator:
await communicator.put('/testdir'.encode(), '1'.encode())
await communicator.put('/testdir/foo'.encode(), '2'.encode())
await communicator.put('/testdir/bar'.encode(), '3'.encode())
await communicator.put('/testdir/foo/baz'.encode(), '4'.encode())
await asyncio.gather(watch(), update())
# WatchEventType.PUT /testdir 1
# WatchEventType.PUT /testdir/foo 2
# WatchEventType.PUT /testdir/bar 3
# WatchEventType.PUT /testdir/foo/baz 4
```
## Transaction
You can run etcd transaction by calling `EtcdCommunicator.txn(txn)`.
### Constructing compares
Constructing compare operations can be done by comparing `Compare` instance.
```python
from etcd_client import Compare, CompareOp
compares = [
Compare.value('cmpkey1'.encode(), CompareOp.EQUAL, 'foo'.encode()),
Compare.value('cmpkey2'.encode(), CompareOp.GREATER, 'bar'.encode()),
]
```
### Executing transaction calls
```python
async with etcd.connect() as communicator:
await communicator.put('cmpkey1'.encode(), 'foo'.encode())
await communicator.put('cmpkey2'.encode(), 'baz'.encode())
await communicator.put('successkey'.encode(), 'asdf'.encode())
compares = [
Compare.value('cmpkey1'.encode(), CompareOp.EQUAL, 'foo'.encode()),
Compare.value('cmpkey2'.encode(), CompareOp.GREATER, 'bar'.encode()),
]
res = await communicator.txn(Txn().when(compares).and_then([TxnOp.get('successkey'.encode())]))
print(res) # TODO: Need to write response type bindings.
```
## How to build
### Prerequisite
* The Rust development environment (the 2021 edition or later) using [`rustup`](https://rustup.rs/) or your package manager
* The Python development environment (3.10 or later) using [`pyenv`](https://github.com/pyenv/pyenv#installation) or your package manager
### Build instruction
First, create a virtualenv (either using the standard venv package, pyenv, or
whatever your favorite). Then, install the PEP-517 build toolchain and run it.
```shell
pip install -U pip build setuptools
python -m build --sdist --wheel
```
It will automatically install build dependencies like
[`maturin`](https://github.com/PyO3/maturin) and build the wheel and source
distributions under the `dist/` directory.
## How to develop and test
(TODO: run maturin for an editable setup)
Raw data
{
"_id": null,
"home_page": null,
"name": "etcd-client-py",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "etcd, binding, rust, etcd-client",
"author": "Sanghyeon Seo, Joongi Kim",
"author_email": "seosh@lablup.com, joongi@lablup.com",
"download_url": "https://files.pythonhosted.org/packages/ae/d0/f913cd99d088401900c66f9ffc70f34d697e206360e394beb6d7abc91b9c/etcd_client_py-0.4.0.tar.gz",
"platform": null,
"description": "# etcd-client-py\n\n[](https://pypi.org/project/etcd-client-py/)\n\n\nPython wrapper of [etcd_client](https://github.com/etcdv3/etcd-client) built with [PyO3](https://github.com/PyO3/pyo3).\n\n## Installation\n\n```bash\npip install etcd_client\n```\n\n## Basic usage\n\n```python\nfrom etcd_client import EtcdClient\netcd = EtcdClient(['http:://127.0.0.1:2379'])\n```\n\nActual connection establishment with Etcd's gRPC channel will be done when you call `EtcdClient.connect()`.\n\n```python\nasync def main():\n async with etcd.connect() as communicator:\n await communicator.put('testkey'.encode(), 'testvalue'.encode())\n value = await communicator.get('testkey'.encode())\n print(bytes(value).decode()) # testvalue\n```\n\n`EtcdCommunicator.get_prefix(prefix)` will return a tuple of list containing all key-values with given key prefix.\n\n```python\nasync def main():\n async with etcd.connect() as communicator:\n await communicator.put('/testdir'.encode(), 'root'.encode())\n await communicator.put('/testdir/1'.encode(), '1'.encode())\n await communicator.put('/testdir/2'.encode(), '2'.encode())\n await communicator.put('/testdir/2/3'.encode(), '3'.encode())\n\n test_dir = await communicator.get_prefix('/testdir'.encode())\n\n for resp in test_dir:\n # ['/testdir', 'root']\n # ['/testdir/1', '1']\n # ['/testdir/2', '2']\n # ['/testdir/2/3', '3']\n print([bytes(v).decode() for v in resp])\n```\n\n## Operating with Etcd lock\n\nJust like `EtcdClient.connect()`, you can easilly use etcd lock by calling `EtcdClient.with_lock(lock_opts)`.\n\n```python\nasync def first():\n async with etcd.with_lock(\n EtcdLockOption(\n lock_name='foolock'.encode(),\n )\n ) as communicator:\n value = await communicator.get('testkey'.encode())\n print('first:', bytes(value).decode(), end=' | ')\n\nasync def second():\n await asyncio.sleep(0.1)\n async with etcd.with_lock(\n EtcdLockOption(\n lock_name='foolock'.encode(),\n )\n ) as communicator:\n value = await communicator.get('testkey'.encode())\n print('second:', bytes(value).decode())\n\nasync with etcd.connect() as communicator:\n await communicator.put('testkey'.encode(), 'testvalue'.encode())\nawait asyncio.gather(first(), second()) # first: testvalue | second: testvalue\n```\n\nAdding `timeout` parameter to `EtcdClient.with_lock()` call will add a timeout to lock acquiring process.\n\n```python\nasync def first():\n async with etcd.with_lock(\n EtcdLockOption(\n lock_name='foolock'.encode(),\n )\n ) as communicator:\n value = await communicator.get('testkey'.encode())\n print('first:', bytes(value).decode(), end=' | ')\n\nasync def second():\n await asyncio.sleep(0.1)\n async with etcd.with_lock(\n EtcdLockOption(\n lock_name='foolock'.encode(),\n timeout=5.0,\n )\n ) as communicator:\n value = await communicator.get('testkey'.encode())\n print('second:', bytes(value).decode())\n\nasync with etcd.connect() as communicator:\n await communicator.put('testkey'.encode(), 'testvalue'.encode())\nawait asyncio.gather(first(), second()) # first: testvalue | second: testvalue\n```\n\nAdding `ttl` parameter to `EtcdClient.with_lock()` call will force lock to be released after given seconds.\n\n```python\nasync def first():\n async with etcd.with_lock(\n EtcdLockOption(lock_name=\"foolock\".encode(), ttl=5)\n ) as communicator:\n await asyncio.sleep(10)\n\nasync def second():\n start = time.time()\n async with etcd.with_lock(\n EtcdLockOption(lock_name=\"foolock\".encode(), ttl=5)\n ) as communicator:\n print(f\"acquired lock after {time.time() - start} seconds\")\n\n# 'second' acquired lock after 5.247947931289673 seconds\ndone, _ = await asyncio.wait([\n asyncio.create_task(first()),\n asyncio.create_task(second())\n], return_when=asyncio.FIRST_COMPLETED)\n\nfor task in done:\n print(task.result())\n```\n\n## Watch\n\nYou can watch changes on key with `EtcdCommunicator.watch(key)`.\n\n```python\nasync def watch():\n async with etcd.connect() as communicator:\n async for event in communicator.watch('testkey'.encode()):\n print(event.event, bytes(event.value).decode())\n\nasync def update():\n await asyncio.sleep(0.1)\n async with etcd.connect() as communicator:\n await communicator.put('testkey'.encode(), '1'.encode())\n await communicator.put('testkey'.encode(), '2'.encode())\n await communicator.put('testkey'.encode(), '3'.encode())\n await communicator.put('testkey'.encode(), '4'.encode())\n await communicator.put('testkey'.encode(), '5'.encode())\n\nawait asyncio.gather(watch(), update())\n# WatchEventType.PUT 1\n# WatchEventType.PUT 2\n# WatchEventType.PUT 3\n# WatchEventType.PUT 4\n# WatchEventType.PUT 5\n```\n\nWatching changes on keys with specific prefix can be also done by `EtcdCommunicator.watch_prefix(key_prefix)`.\n\n```python\nasync def watch():\n async with etcd.connect() as communicator:\n async for event in communicator.watch_prefix('/testdir'.encode()):\n print(event.event, bytes(event.key).decode(), bytes(event.value).decode())\n\nasync def update():\n await asyncio.sleep(0.1)\n async with etcd.connect() as communicator:\n await communicator.put('/testdir'.encode(), '1'.encode())\n await communicator.put('/testdir/foo'.encode(), '2'.encode())\n await communicator.put('/testdir/bar'.encode(), '3'.encode())\n await communicator.put('/testdir/foo/baz'.encode(), '4'.encode())\n\nawait asyncio.gather(watch(), update())\n# WatchEventType.PUT /testdir 1\n# WatchEventType.PUT /testdir/foo 2\n# WatchEventType.PUT /testdir/bar 3\n# WatchEventType.PUT /testdir/foo/baz 4\n```\n\n## Transaction\n\nYou can run etcd transaction by calling `EtcdCommunicator.txn(txn)`.\n\n### Constructing compares\n\nConstructing compare operations can be done by comparing `Compare` instance.\n\n```python\nfrom etcd_client import Compare, CompareOp\ncompares = [\n Compare.value('cmpkey1'.encode(), CompareOp.EQUAL, 'foo'.encode()),\n Compare.value('cmpkey2'.encode(), CompareOp.GREATER, 'bar'.encode()),\n]\n```\n\n### Executing transaction calls\n\n```python\nasync with etcd.connect() as communicator:\n await communicator.put('cmpkey1'.encode(), 'foo'.encode())\n await communicator.put('cmpkey2'.encode(), 'baz'.encode())\n await communicator.put('successkey'.encode(), 'asdf'.encode())\n\n compares = [\n Compare.value('cmpkey1'.encode(), CompareOp.EQUAL, 'foo'.encode()),\n Compare.value('cmpkey2'.encode(), CompareOp.GREATER, 'bar'.encode()),\n ]\n\n res = await communicator.txn(Txn().when(compares).and_then([TxnOp.get('successkey'.encode())]))\n print(res) # TODO: Need to write response type bindings.\n```\n\n## How to build\n\n### Prerequisite\n\n* The Rust development environment (the 2021 edition or later) using [`rustup`](https://rustup.rs/) or your package manager\n* The Python development environment (3.10 or later) using [`pyenv`](https://github.com/pyenv/pyenv#installation) or your package manager\n\n### Build instruction\n\nFirst, create a virtualenv (either using the standard venv package, pyenv, or\nwhatever your favorite). Then, install the PEP-517 build toolchain and run it.\n\n```shell\npip install -U pip build setuptools\npython -m build --sdist --wheel\n```\n\nIt will automatically install build dependencies like\n[`maturin`](https://github.com/PyO3/maturin) and build the wheel and source\ndistributions under the `dist/` directory.\n\n## How to develop and test\n\n(TODO: run maturin for an editable setup)\n\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Yet another etcd-client API binding based on the Rust's etcd-client package",
"version": "0.4.0",
"project_urls": {
"homepage": "https://github.com/lablup/etcd-client-py",
"repository": "https://github.com/lablup/etcd-client-py"
},
"split_keywords": [
"etcd",
" binding",
" rust",
" etcd-client"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "0c994791372122452557abeb5b43003a2fcf5f4537ac25eb3b545e01634a0222",
"md5": "19fe037f4d3398d1386c97d2909cdf92",
"sha256": "d36a34a1e6a53b62408c4fcce35d643a6572f363fd21f0a9ae6912924b5b7993"
},
"downloads": -1,
"filename": "etcd_client_py-0.4.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl",
"has_sig": false,
"md5_digest": "19fe037f4d3398d1386c97d2909cdf92",
"packagetype": "bdist_wheel",
"python_version": "cp311",
"requires_python": ">=3.10",
"size": 3401382,
"upload_time": "2025-02-21T02:41:59",
"upload_time_iso_8601": "2025-02-21T02:41:59.879103Z",
"url": "https://files.pythonhosted.org/packages/0c/99/4791372122452557abeb5b43003a2fcf5f4537ac25eb3b545e01634a0222/etcd_client_py-0.4.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "bed3025277cd4a4c1046aa4546706be6b49f3aba849cb2507920d11b600764d9",
"md5": "4b3e3c27ee2d33f0179273f31344b072",
"sha256": "396f7e910f9ed8996a3d395a99be2dc10136472d31e5bcd920b7d7b713901791"
},
"downloads": -1,
"filename": "etcd_client_py-0.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"has_sig": false,
"md5_digest": "4b3e3c27ee2d33f0179273f31344b072",
"packagetype": "bdist_wheel",
"python_version": "cp311",
"requires_python": ">=3.10",
"size": 1855715,
"upload_time": "2025-02-21T02:42:02",
"upload_time_iso_8601": "2025-02-21T02:42:02.845521Z",
"url": "https://files.pythonhosted.org/packages/be/d3/025277cd4a4c1046aa4546706be6b49f3aba849cb2507920d11b600764d9/etcd_client_py-0.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "b761aa58d62067bca5796ff7a2e9c05f3acfd5da0b06e141a5ea3aad5d0ae2cf",
"md5": "abe6d0cdce32289fe486d19eb0162a78",
"sha256": "332af0ca13c05c129d27187edce5ad94b04d8bd10a114495b9d9c7bbd4c3d7a6"
},
"downloads": -1,
"filename": "etcd_client_py-0.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"has_sig": false,
"md5_digest": "abe6d0cdce32289fe486d19eb0162a78",
"packagetype": "bdist_wheel",
"python_version": "cp311",
"requires_python": ">=3.10",
"size": 1854229,
"upload_time": "2025-02-21T02:42:07",
"upload_time_iso_8601": "2025-02-21T02:42:07.779851Z",
"url": "https://files.pythonhosted.org/packages/b7/61/aa58d62067bca5796ff7a2e9c05f3acfd5da0b06e141a5ea3aad5d0ae2cf/etcd_client_py-0.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "32f59b11243ee9a01f70b0a8cc984a91bec467d60c3a683688cbb2c329ebe5fa",
"md5": "caee8dd31e078ac68a23c0d11557fa4f",
"sha256": "9452556bab4da1d282d69a7ee1b9f4520b345e1e5572f801cb61a5864984fb6c"
},
"downloads": -1,
"filename": "etcd_client_py-0.4.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl",
"has_sig": false,
"md5_digest": "caee8dd31e078ac68a23c0d11557fa4f",
"packagetype": "bdist_wheel",
"python_version": "cp312",
"requires_python": ">=3.10",
"size": 3384792,
"upload_time": "2025-02-21T02:42:10",
"upload_time_iso_8601": "2025-02-21T02:42:10.488616Z",
"url": "https://files.pythonhosted.org/packages/32/f5/9b11243ee9a01f70b0a8cc984a91bec467d60c3a683688cbb2c329ebe5fa/etcd_client_py-0.4.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "5acdc3d956d606dd11cf2e200fe7c4ff55f01196fd7d29299121e1b565b1c5b6",
"md5": "e270c94a0324c264eb98ec2d8d356dda",
"sha256": "1ef36898275fc952ff152b3a7be5a9791d6363f75e17e2755148862ae211f31c"
},
"downloads": -1,
"filename": "etcd_client_py-0.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"has_sig": false,
"md5_digest": "e270c94a0324c264eb98ec2d8d356dda",
"packagetype": "bdist_wheel",
"python_version": "cp312",
"requires_python": ">=3.10",
"size": 1861501,
"upload_time": "2025-02-21T02:42:13",
"upload_time_iso_8601": "2025-02-21T02:42:13.405052Z",
"url": "https://files.pythonhosted.org/packages/5a/cd/c3d956d606dd11cf2e200fe7c4ff55f01196fd7d29299121e1b565b1c5b6/etcd_client_py-0.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "8663b4d0bc3bd4da3b69dcb7d0aee9d0780775f7f076e7678e40f58c85c1fe69",
"md5": "0062a0cd075c2562774565e9e10191c8",
"sha256": "c96317cff8537ec2506110651b078e0944e25aba32f3c0a1b1a00297d4dd6576"
},
"downloads": -1,
"filename": "etcd_client_py-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"has_sig": false,
"md5_digest": "0062a0cd075c2562774565e9e10191c8",
"packagetype": "bdist_wheel",
"python_version": "cp312",
"requires_python": ">=3.10",
"size": 1858781,
"upload_time": "2025-02-21T02:42:16",
"upload_time_iso_8601": "2025-02-21T02:42:16.140514Z",
"url": "https://files.pythonhosted.org/packages/86/63/b4d0bc3bd4da3b69dcb7d0aee9d0780775f7f076e7678e40f58c85c1fe69/etcd_client_py-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "fb738eb166af8c1715e2fe49763dba83e7c855c2d2b41ebf81b77b941d91c79f",
"md5": "2af2071a52cbc3bcd83d0ff29b7c3c7b",
"sha256": "dd78db26b4f9d2f68c726185e271049f0fa9538cd41e59b9af966c1678ac253b"
},
"downloads": -1,
"filename": "etcd_client_py-0.4.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl",
"has_sig": false,
"md5_digest": "2af2071a52cbc3bcd83d0ff29b7c3c7b",
"packagetype": "bdist_wheel",
"python_version": "cp313",
"requires_python": ">=3.10",
"size": 3384545,
"upload_time": "2025-02-21T02:42:19",
"upload_time_iso_8601": "2025-02-21T02:42:19.053932Z",
"url": "https://files.pythonhosted.org/packages/fb/73/8eb166af8c1715e2fe49763dba83e7c855c2d2b41ebf81b77b941d91c79f/etcd_client_py-0.4.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "d5569f3ede4b30d91ff24ec7500ebdcad7c584c152aa886900881e6539709915",
"md5": "0fd41552b1e8fda184ca7400495b74ac",
"sha256": "481151f77e64132f6078508c8fc0d0b8384ecdbcdd80d792aaa2c123a82f0e1f"
},
"downloads": -1,
"filename": "etcd_client_py-0.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"has_sig": false,
"md5_digest": "0fd41552b1e8fda184ca7400495b74ac",
"packagetype": "bdist_wheel",
"python_version": "cp313",
"requires_python": ">=3.10",
"size": 1860320,
"upload_time": "2025-02-21T02:42:21",
"upload_time_iso_8601": "2025-02-21T02:42:21.742727Z",
"url": "https://files.pythonhosted.org/packages/d5/56/9f3ede4b30d91ff24ec7500ebdcad7c584c152aa886900881e6539709915/etcd_client_py-0.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "0939141c19ac445ac4bbcada84d074baab21a6d33c2d22cf2ebdb76e3a47bad7",
"md5": "8e3e9603d3c595398867fb151760fced",
"sha256": "567b0c651d4dd43ce3c228cea47b3f7c4cf0e074e3a8b7b565e4b4a5f792f255"
},
"downloads": -1,
"filename": "etcd_client_py-0.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"has_sig": false,
"md5_digest": "8e3e9603d3c595398867fb151760fced",
"packagetype": "bdist_wheel",
"python_version": "cp313",
"requires_python": ">=3.10",
"size": 1858593,
"upload_time": "2025-02-21T02:42:27",
"upload_time_iso_8601": "2025-02-21T02:42:27.237298Z",
"url": "https://files.pythonhosted.org/packages/09/39/141c19ac445ac4bbcada84d074baab21a6d33c2d22cf2ebdb76e3a47bad7/etcd_client_py-0.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "aed0f913cd99d088401900c66f9ffc70f34d697e206360e394beb6d7abc91b9c",
"md5": "1f57ac8f20ba5d7907d8dfb57bc6386f",
"sha256": "a38998fd084ac80bbcf08d526dc4ea14cabf20ad35c844d6ebbfc52c2eaebf1b"
},
"downloads": -1,
"filename": "etcd_client_py-0.4.0.tar.gz",
"has_sig": false,
"md5_digest": "1f57ac8f20ba5d7907d8dfb57bc6386f",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 31581,
"upload_time": "2025-02-21T02:42:29",
"upload_time_iso_8601": "2025-02-21T02:42:29.763840Z",
"url": "https://files.pythonhosted.org/packages/ae/d0/f913cd99d088401900c66f9ffc70f34d697e206360e394beb6d7abc91b9c/etcd_client_py-0.4.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-02-21 02:42:29",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "lablup",
"github_project": "etcd-client-py",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [
{
"name": "maturin",
"specs": [
[
"==",
"1.3.2"
]
]
},
{
"name": "pytest",
"specs": [
[
"==",
"7.3.1"
]
]
},
{
"name": "trafaret",
"specs": [
[
"~=",
"2.1"
]
]
}
],
"lcname": "etcd-client-py"
}