etcd-client-py


Nameetcd-client-py JSON
Version 0.4.0 PyPI version JSON
download
home_pageNone
SummaryYet another etcd-client API binding based on the Rust's etcd-client package
upload_time2025-02-21 02:42:29
maintainerNone
docs_urlNone
authorSanghyeon Seo, Joongi Kim
requires_python>=3.10
licenseMIT
keywords etcd binding rust etcd-client
VCS
bugtrack_url
requirements maturin pytest trafaret
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # etcd-client-py

[![PyPI release version](https://badge.fury.io/py/etcd-client-py.svg)](https://pypi.org/project/etcd-client-py/)
![Wheels](https://img.shields.io/pypi/wheel/etcd-client-py.svg)

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[![PyPI release version](https://badge.fury.io/py/etcd-client-py.svg)](https://pypi.org/project/etcd-client-py/)\n![Wheels](https://img.shields.io/pypi/wheel/etcd-client-py.svg)\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"
}
        
Elapsed time: 1.43662s