Git Product home page Git Product logo

pythonetcdclient's Introduction

Introduction

PEC was created as a more elegant and proper client for etcd than existing solutions. It has an intuitive construction, provides access to the complete etcd API (of 0.2.0+), and just works.

Every request returns a standard and obvious result, and HTTP exceptions are re-raised as Python-standard exceptions where it makes sense (like "KeyError").

The full API is documented here.

Quick Start

There's almost nothing to it:

from etcd.client import Client

# Uses the default *etcd* port on *localhost* unless told differently.
c = Client()

c.node.set('/test/key', 5)

r = c.node.get('/test/key')

print(r.node.value)
# Displays "5".

r = c.node.set('/test/key', 10)
print(r.prev_node.value)
# Displays "5".

SSL

PEC also allows for SSL authentication and encrypted communication.

To use it for communication, pass the hostname as the host parameter and a is_ssl of True. If you need to pass a bundle of CA certificates (for less well-known root authorities), pass ssl_ca_bundle_filepath.

c = Client(host='etcd.local', 
           is_ssl=True, 
           ssl_ca_bundle_filepath='ssl/rootCA.pem')

General Functions

These functions represent the basic key-value functionality of etcd.

Set a value:

# Can provide a "ttl" parameter with seconds, for expiration.
r = c.node.set('/node_test/subkey1', 5)

print(r)
# Prints: <RESPONSE: <NODE(ResponseV2AliveNode) [set] [/node_test/subkey1] 
#           IS_HID=[False] IS_DEL=[False] IS_DIR=[False] IS_COLL=[False] 
#           TTL=[None] CI=(5) MI=(5)>>

Get a value:

r = c.node.get('/node_test/subkey1')

print(r)
# Prints: <RESPONSE: <NODE(ResponseV2AliveNode) [get] [/node_test/subkey1] 
#           IS_HID=[False] IS_DEL=[False] IS_DIR=[False] IS_COLL=[False] 
#           TTL=[None] CI=(4) MI=(4)>>

print(r.node.value)
# Prints "5"

Wait for a change to a specific node:

r = c.node.wait('/node_test/subkey1')

print(r)
# Prints: <RESPONSE: <NODE(ResponseV2AliveNode) [set] [/node_test/subkey1] 
#           IS_HID=[False] IS_DEL=[False] IS_DIR=[False] IS_COLL=[False] 
#           TTL=[None] CI=(5) MI=(5)>>

print(r.node.value)
# Prints "20"

In this case, a set with a value of (20) was performed from another terminal, and we were given the same exact response that they got. We can set the recursive parameter to True to watch subdirectories and subdirectories-of- subdirectories as well.

Get children:

r = c.node.set('/node_test/subkey2', 10)

print(r)
# Prints: <RESPONSE: <NODE(ResponseV2AliveNode) [set] [/node_test/subkey2] 
#           IS_HID=[False] IS_DEL=[False] IS_DIR=[False] IS_COLL=[False] 
#           TTL=[None] CI=(6) MI=(6)>>

r = c.node.get('/node_test')

print(r)
# Prints: <RESPONSE: <NODE(ResponseV2AliveDirectoryNode) [get] [/node_test] 
#           IS_HID=[False] TTL=[None] IS_DIR=[True] IS_COLL=[True] 
#           COUNT=[2] CI=(5) MI=(5)>>

Get children, recursively:

r = c.node.get('/node_test', recursive=True)

print(r)
# Prints: <RESPONSE: <NODE(ResponseV2AliveDirectoryNode) [get] [/node_test] 
#           IS_HID=[False] TTL=[None] IS_DIR=[True] IS_COLL=[True] 
#           COUNT=[2] CI=(5) MI=(5)>>

for node in r.node.children:
    print(node)

# Prints:
# <NODE(ResponseV2AliveNode) [get] [/node_test/subkey1] IS_HID=[False] 
#   IS_DEL=[False] IS_DIR=[False] IS_COLL=[False] TTL=[None] CI=(5) MI=(5)>
# <NODE(ResponseV2AliveNode) [get] [/node_test/subkey2] IS_HID=[False] 
#   IS_DEL=[False] IS_DIR=[False] IS_COLL=[False] TTL=[None] CI=(6) MI=(6)>

Delete node:

r = c.node.delete('/node_test/subkey2')

print(r)
# Prints: <RESPONSE: <NODE(ResponseV2DeletedNode) [delete] 
#           [/node_test/subkey2] IS_HID=[False] IS_DEL=[True] 
#           IS_DIR=[False] IS_COLL=[False] TTL=[None] CI=(6) MI=(7)>>

Compare and Swap (CAS) Functions

These functions represent etcd's atomic comparisons. These allow for a "set"- type operation when one or more conditions are met.

The core call takes one or more of the following conditions as arguments:

current_value
prev_exists
current_index

If none of the conditions are given, the call acts like a set(). If a condition is given that fails, a etcd.exceptions.EtcdPreconditionException is raised.

The core call is:

r = c.node.compare_and_swap('/cas_test/val1', 30, current_value=5, 
                            prev_exists=True, current_index=5)

The following convenience functions are also provided. They only allow you to check one, specific condition:

r = c.node.create_only('/cas_test/val1', 5)

print(r)
# Prints: <RESPONSE: <NODE(ResponseV2AliveNode) [create] [/cas_test/val1] 
#           IS_HID=[False] IS_DEL=[False] IS_DIR=[False] IS_COLL=[False] 
#           TTL=[None] CI=(10) MI=(10)>>

r = c.node.update_only('/cas_test/val1', 10)

print(r)
# Prints: <RESPONSE: <NODE(ResponseV2AliveNode) [update] [/cas_test/val1] 
#           IS_HID=[False] IS_DEL=[False] IS_DIR=[False] IS_COLL=[False] 
#           TTL=[None] CI=(10) MI=(13)>>

r = c.node.update_if_index('/cas_test/val1', 15, r.node.modified_index)

print(r)
# Prints: <RESPONSE: <NODE(ResponseV2AliveNode) [compareAndSwap] 
#           [/cas_test/val1] IS_HID=[False] IS_DEL=[False] IS_DIR=[False] 
#           IS_COLL=[False] TTL=[None] CI=(10) MI=(14)>>

r = c.node.update_if_value('/cas_test/val1', 20, 15)

print(r)
# Prints: <RESPONSE: <NODE(ResponseV2AliveNode) [compareAndSwap] 
#           [/cas_test/val1] IS_HID=[False] IS_DEL=[False] IS_DIR=[False] 
#           IS_COLL=[False] TTL=[None] CI=(10) MI=(15)>>

Directory Functions

These functions represent directory-specific calls. Whereas creating a node has side-effects that contribute to directory management (like creating a node under a directory implicitly creates the directory), these functions are directory specific.

Create directory:

# Can provide a "ttl" parameter with seconds, for expiration.
r = c.directory.create('/dir_test/new_dir')

print(r)
# Prints: <RESPONSE: <NODE(ResponseV2AliveDirectoryNode) [set] 
#           [/dir_test/new_dir] IS_HID=[False] TTL=[None] IS_DIR=[True] 
#           IS_COLL=[False] COUNT=[<NA>] CI=(16) MI=(16)>

Remove an empty directory:

r = c.directory.delete('/dir_test/new_dir')

print(r)
# Prints: <RESPONSE: <NODE(ResponseV2DeletedDirectoryNode) [delete] 
#           [/dir_test/new_dir] IS_HID=[False] IS_DEL=[True] IS_DIR=[True] 
#           IS_COLL=[False] TTL=[None] CI=(16) MI=(17)>>

Recursively remove a directory, and any contents:

c.directory.create('/dir_test/new_dir')
c.directory.create('/dir_test/new_dir/new_subdir')

# This will raise a requests.exceptions.HTTPError ("403 Client Error: 
# Forbidden") because it has children.
r = c.directory.delete('/dir_test/new_dir')

# You have to recursively delete it.
r = c.directory.delete_recursive('/dir_test')

print(r)
# Prints: <RESPONSE: <NODE(ResponseV2DeletedDirectoryNode) [delete] 
#           [/dir_test] IS_HID=[False] IS_DEL=[True] IS_DIR=[True] 
#           IS_COLL=[False] TTL=[None] CI=(16) MI=(20)>>

Compare and Delete (CAD)

Similar to the "compare and swap" functionality (mentioned above), compare-and-delete functionality (introduced in etcd 0.3.0) is also exposed. This functionality allows you to delete a node only if its value or index meets conditions. Unlike the CAS functionality, CAD functionality is located in the node and directory functions.

r = c.node.delete_if_value('/node_test/testkey', 'some_existing_value')

print(r)
# Prints: <RESPONSE: <NODE(ResponseV2DeletedNode) [compareAndDelete] 
#           [/node_test/testkey] IS_HID=[False] IS_DEL=[True] IS_DIR=[False] 
#           IS_COLL=[False] TTL=[None] CI=(35) MI=(36)>>

r = c.node.delete_if_index('/node_test/testkey2', 22)

print(r)
# (waiting on bugfixes, to test)

r = c.directory.delete_if_index('/dir_test/testkey2', 22)

print(r)
# (waiting on bugfixes, to test)

r = c.directory.delete_recursive_if_index('/dir_test/testkey2', 22)

print(r)
# (waiting on bugfixes, to test)


Server Functions
----------------

These functions represent calls that return information about the server or 
cluster.

Get version of the specific host being connected to:

```python
r = c.server.get_version()

print(r)
# Prints "0.2.0-45-g98351b9", on my system.

The URL prefix of the current cluster leader:

r = c.server.get_leader_url_prefix()

print(r)
# Prints "http://127.0.0.1:7001" with my single-host configuration.

Enumerate the prefixes of the hosts in the cluster:

machines = c.server.get_machines()

print(machines)
# Prints: [(u'etcd', u'http://127.0.0.1:4001'), 
#          (u'raft', u'http://127.0.0.1:7001')]

Get URL of the dashboard for the server being connected-to:

r = c.server.get_dashboard_url()

print(r)
# Prints: http://127.0.0.1:4001/mod/dashboard

In-Order-Keys Functions

These calls represent the in-order functionality, where a directory can be used to store a series of values with automatically-assigned, increasing keys. Though not quite sufficient as a queue, this might be used to automatically generate unique keys for a set of values.

Enqueue values:

io = c.inorder.get_inorder('/queue_test')

io.add('value1')
io.add('value2')

Enumerate existing values:

# If you want to specifically return the entries in order of the keys 
# (which is to say that they're in insert-order), use the "sorted"
# parameter.
r = io.list()

for child in r.node.children:
    print(child.value)

# Prints:
# value1
# value2

Statistics Functions

This functions provide access to the statistics information published by the etcd hosts.

s = c.stat.get_leader_stats()

print(s)
# Prints: (u'test01', {u'dustinlenovo': LStatFollower(counts=
#         LStatCounts(fail=412, success=75214), latency=
#         LStatLatency(average=7.201827094703149, current=0.535978, 
#         maximum=350.543234, minimum=0.462994, 
#         standard_deviation=22.639299448915402))})

s = c.stat.get_self_stats()

print(s)
# Prints: SStat(leader_info=SStatLeader(leader=u'test01', 
#         uptime=datetime.timedelta(0, 3971, 790306)), name=u'test01', 
#         recv_append_request_cnt=0, send_append_request_cnt=75626, 
#         send_bandwidth_rate=538.5960990745054, 
#         send_pkg_rate=20.10061948402707, 
#         start_time=datetime.datetime(2014, 2, 8, 16, 26, 13), 
#         state=u'leader')

Locking Module Functions

These functions represent the fair locking functionality that comes packaged.

Standard Locking

A simple, distributed lock:

l = c.module.lock.get_lock('test_lock_1', ttl=10)
l.acquire()
l.renew(ttl=30)
l.release()

This returns the index of the current lock holder:

l.get_active_index()

It's also available as a with statement:

with c.module.lock.get_lock('test_lock_1', ttl=10):
    print("In lock 1.")

Reentrant Locking

Here, a name for the lock is provided, as well as a value that represents a certain locking purpose, process, or host. Subsequent requests having the same value currently stored for the lock will return immediately, where others will block until the current lock has been released or expired.

This can be used when certain parts of the logic need to participate during the same lock, or a specific function/etc might be invoked multiple times during a lock.

This is the basic usage (nearly identical to the traditional lock):

rl = c.module.lock.get_rlock('test_lock_2', 'proc1', ttl=10)
rl.acquire()
rl.renew(ttl=30)
rl.release()

This returns the current value of the lock holder(s):

rl.get_active_value()

This is also provided as a with statement:

with c.module.lock.get_rlock('test_lock_2', 'proc1', ttl=10):
    print("In lock 2.")

Leader Election Module Functions

The leader-election API does consensus-based assignment, meaning that, of all of the clients potentially attempting to assign a value to a name, only one assignment will be allowed for a certain period of time, or until the key is deleted.

To set or renew a value:

c.module.leader.set_or_renew('consensus-based key', 'test value', ttl=10)

To get the current value:

# This will return:
# > A non-empty string if the key is set and unexpired.
# > None, if the key was set but has been expired or deleted.
r = c.module.leader.get('consensus-based key')

print(r)
# Prints "test value".

To delete the value (will fail unless there's an unexpired value):

c.module.leader.delete('consensus-based key', 'test value')

pythonetcdclient's People

Contributors

dsoprea avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

pythonetcdclient's Issues

ImportError: No module named simplejson

$ python
Python 2.7.10 (default, May 25 2015, 13:06:17)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from etcd.client import Client
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/etcd/client.py", line 13, in <module>
    from etcd.node_ops import NodeOps
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/etcd/node_ops.py", line 11, in <module>
    from etcd.response import ResponseV2
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/etcd/response.py", line 2, in <module>
    import simplejson
ImportError: No module named simplejson

Some wrong with using moudle

Hi, Here is my question:
I had installed the etcd with pip, which specify 'pip install etcd' , no thing going wrong looks like. Howerver when using it, after import etcd sucessefully , etcd.Client() not working, it said module etcd doesn't have the attribute named Client.
At first, I think maybe import wrong module which have same name . I used dir() to find the information of etcd. I find that the set of import is right, however there is noting in the etcd module. Then I try the source code install. and get the same result. By the way , etcd3 module seems good. I don't know why this happend? can anyone gives some advice?
this is my code and the test of etcd
image

image

Really wired , nerver happened before

Installation is not working

Hi! Just tried to install this lib with poetry
Python 3.11, macOS

poetry add etcd
Using version ^2.0.8 for etcd

Updating dependencies
Resolving dependencies... (0.1s)

Package operations: 2 installs, 0 updates, 0 removalsInstalling pytz (2013.8): Failed

  ChefBuildError

  Backend subprocess exited when trying to invoke get_requires_for_build_wheel
  
  Traceback (most recent call last):
    File "/private/var/folders/9s/b20x6jx11vn1n56s9psrw6mr0000gn/T/tmpxkthggaj/pytz-2013.8/pytz/lazy.py", line 3, in <module>
      from UserDict import DictMixin
  ModuleNotFoundError: No module named 'UserDict'
  
  During handling of the above exception, another exception occurred:
  
  Traceback (most recent call last):
    File "/Users/fannigurt/Library/Application Support/pypoetry/venv/lib/python3.11/site-packages/pyproject_hooks/_in_process/_in_process.py", line 353, in <module>
      main()
    File "/Users/fannigurt/Library/Application Support/pypoetry/venv/lib/python3.11/site-packages/pyproject_hooks/_in_process/_in_process.py", line 335, in main
      json_out['return_val'] = hook(**hook_input['kwargs'])
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "/Users/fannigurt/Library/Application Support/pypoetry/venv/lib/python3.11/site-packages/pyproject_hooks/_in_process/_in_process.py", line 118, in get_requires_for_build_wheel
      return hook(config_settings)
             ^^^^^^^^^^^^^^^^^^^^^
    File "/private/var/folders/9s/b20x6jx11vn1n56s9psrw6mr0000gn/T/tmp4gnb0f22/.venv/lib/python3.11/site-packages/setuptools/build_meta.py", line 341, in get_requires_for_build_wheel
      return self._get_build_requires(config_settings, requirements=['wheel'])
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "/private/var/folders/9s/b20x6jx11vn1n56s9psrw6mr0000gn/T/tmp4gnb0f22/.venv/lib/python3.11/site-packages/setuptools/build_meta.py", line 323, in _get_build_requires
      self.run_setup()
    File "/private/var/folders/9s/b20x6jx11vn1n56s9psrw6mr0000gn/T/tmp4gnb0f22/.venv/lib/python3.11/site-packages/setuptools/build_meta.py", line 488, in run_setup
      self).run_setup(setup_script=setup_script)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "/private/var/folders/9s/b20x6jx11vn1n56s9psrw6mr0000gn/T/tmp4gnb0f22/.venv/lib/python3.11/site-packages/setuptools/build_meta.py", line 338, in run_setup
      exec(code, locals())
    File "<string>", line 5, in <module>
    File "/private/var/folders/9s/b20x6jx11vn1n56s9psrw6mr0000gn/T/tmpxkthggaj/pytz-2013.8/pytz/__init__.py", line 37, in <module>
      from pytz.lazy import LazyDict, LazyList, LazySet
    File "/private/var/folders/9s/b20x6jx11vn1n56s9psrw6mr0000gn/T/tmpxkthggaj/pytz-2013.8/pytz/lazy.py", line 5, in <module>
      from collections import Mapping as DictMixin
  ImportError: cannot import name 'Mapping' from 'collections' (/opt/homebrew/Cellar/python@3.11/3.11.2_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/collections/__init__.py)
  

  at ~/Library/Application Support/pypoetry/venv/lib/python3.11/site-packages/poetry/installation/chef.py:152 in _prepare
      148149error = ChefBuildError("\n\n".join(message_parts))
      150151if error is not None:
    → 152raise error from None
      153154return path
      155156def _prepare_sdist(self, archive: Path, destination: Path | None = None) -> Path:

Note: This error originates from the build backend, and is likely not a problem with poetry but with pytz (2013.8) not supporting PEP 517 builds. You can verify this by running 'pip wheel --use-pep517 "pytz (==2013.8)"'.

import error:No module named packages.urllib3.poolmanager

root@6000t14:~/.ssh# python
Python 2.7.6 (default, Jun 22 2015, 17:58:13)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.

from etcd.client import Client
Traceback (most recent call last):
File "", line 1, in
File "/usr/local/lib/python2.7/dist-packages/etcd/client.py", line 8, in
from requests.packages.urllib3.poolmanager import PoolManager
ImportError: No module named packages.urllib3.poolmanager
import requests
help(requests.packages)
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'module' object has no attribute 'packages'
quit()

Installing from pypi fails due to missing requirements.txt

$ pip install etcd
Downloading/unpacking etcd
  Downloading etcd-2.0.8.tar.gz
  Running setup.py egg_info for package etcd
    Traceback (most recent call last):
      File "<string>", line 14, in <module>
      File "/build/etcd/setup.py", line 13, in <module>
        with open(os.path.join(app_path, 'resources', 'requirements.txt')) as f:
    IOError: [Errno 2] No such file or directory: 'etcd/resources/requirements.txt'
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):

  File "<string>", line 14, in <module>

  File "/build/etcd/setup.py", line 13, in <module>

    with open(os.path.join(app_path, 'resources', 'requirements.txt')) as f:

IOError: [Errno 2] No such file or directory: 'etcd/resources/requirements.txt'

----------------------------------------
Command python setup.py egg_info failed with error code 1

Errors with new etcd due to use of private "_etcd" keys

This library access information at /v2/_etcd which is no longer made available, and so does not work with the latest versions on etcd. This functionality, which tries to automatically select a different host when there is a problem, doesn't seem to work for me anyway, and I think it is rather against the etcd API as documented. I would suggest to take this out, or make it optional, or at least to update it so that it is compatible with the latest versions of the etcd server.

etcd-io/etcd#1279

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.