Skip to content

Commit

Permalink
Merge pull request #23 from Revolution1/doc/stateful-and-usage
Browse files Browse the repository at this point in the history
add api doc to stateuful utils
  • Loading branch information
Revolution1 committed Mar 27, 2018
2 parents 07a92d8 + 23941f0 commit 5df58ff
Show file tree
Hide file tree
Showing 13 changed files with 310 additions and 54 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# This file will be regenerated if you run travis_pypi_setup.py

language: python
cache: pip

python:
- 3.6
Expand Down
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ Notice: The authentication header through gRPC-JSON-Gateway only supported in [e
* [x] Support python2.7 and python3.5+
* [x] Sync client based on requests
* [x] Async client based on aiohttp
* [x] Support etcd3 gRPC-JSON-Gateway including stream
* [x] Response modelizing based on etcd3's swagger spec
* [x] Generate code from swagger spec
* [x] TLS Connection
* [x] support APIs
* [x] Auth
Expand Down
13 changes: 13 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@
'Miscellaneous'),
]


# Documents to append as an appendix to all manuals.
# texinfo_appendices = []

Expand All @@ -299,3 +300,15 @@

# If true, do not generate a @detailmenu in the "Top" node's menu.
# texinfo_no_detailmenu = False

def skip(app, what, name, obj, skip, options):
if name in ["__init__", '__enter__', "__exit__", '__aenter__', "__aexit__"]:
return False
return skip


def setup(app):
app.connect("autodoc-skip-member", skip)


autodoc_member_order = 'bysource'
12 changes: 10 additions & 2 deletions docs/etcd3.errors.rst
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
etcd3\.errors\.errors
----------------------------
---------------------

.. automodule:: etcd3.errors.errors
:members:
:undoc-members:
:show-inheritance:

etcd3\.errors\.go\_etcd\_rpctypes\_error
----------------------------------------

.. automodule:: etcd3.errors.go_etcd_rpctypes_error
:members:
:undoc-members:
:show-inheritance:

etcd3\.errors\.go\_grpc\_codes
-------------------------------------
------------------------------

.. automodule:: etcd3.errors.go_grpc_codes
:members:
Expand Down
31 changes: 31 additions & 0 deletions docs/etcd3.stateful.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
etcd3\.stateful\.lease
----------------------

.. automodule:: etcd3.stateful.lease
:members:
:undoc-members:
:show-inheritance:

etcd3\.stateful\.transaction
----------------------------

.. automodule:: etcd3.stateful.transaction
:members:
:undoc-members:
:show-inheritance:

etcd3\.stateful\.watch
----------------------

.. automodule:: etcd3.stateful.watch
:members:
:undoc-members:
:show-inheritance:

etcd3\.stateful\.lock
---------------------

.. automodule:: etcd3.stateful.lock
:members:
:undoc-members:
:show-inheritance:
16 changes: 16 additions & 0 deletions docs/modules.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
API Reference
=============

etcd3\.baseclient
-----------------

.. automodule:: etcd3.baseclient
:members:
:undoc-members:
:show-inheritance:

etcd3\.client
-------------

Expand All @@ -17,6 +25,14 @@ etcd3\.aio\_client
:undoc-members:
:show-inheritance:

etcd3\.stateful
---------------

.. toctree::
:maxdepth: 3

etcd3.stateful

etcd3\.apis
-----------

Expand Down
3 changes: 2 additions & 1 deletion etcd3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@

__all__ = ['__version__', '__author__', '__email__']

from .baseclient import BaseClient
from .client import Client

AioClient = None
if six.PY3: # pragma: no cover
from .aio_client import AioClient

__all__.extend([
'BaseClient',
'Client',
'AioClient'
])
Expand Down
49 changes: 48 additions & 1 deletion etcd3/stateful/lease.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,20 @@ def __init__(self, client, ttl, ID=0, new=True):

@property
def ID(self):
"""
Property: the id of the granted lease
:return: int
"""
return self._ID

def grant(self):
"""
Grant the lease if new is set to False
or it just inherit the lease of the specified id
When granting new lease if ID is set to 0, the lessor will chooses an ID.
"""
if self.new:
r = self.client.lease_grant(self.grantedTTL, self.ID)
self.last_grant = time.time()
Expand All @@ -56,23 +67,50 @@ def grant(self):
return r

def time_to_live(self, keys=False):
"""
Retrieves lease information.
:type keys: bool
:param keys: whether return the keys that attached to the lease
"""
return self.client.lease_time_to_live(self.ID, keys=keys)

def ttl(self):
"""
Get the ttl that lease has left
:return: int
"""
r = self.time_to_live()
if 'TTL' not in r:
return -1
return r.TTL

def alive(self):
"""
Tell if the lease is still alive
:return: bool
"""
return self.ttl() > 0

def keepalive_once(self):
"""
Call keepalive for once to refresh the ttl of the lease
"""
return self.client.lease_keep_alive_once(self.ID)

refresh = keepalive_once

def keepalive(self, keep_cb=None, cancel_cb=None):
"""
Start a daemon thread to constantly keep the lease alive
:type keep_cb: callable
:param keep_cb: callback function that will be called after every refresh
:type cancel_cb: callable
:param cancel_cb: callback function that will be called after cancel keepalive
"""
self.keeping = True

def keepalived():
Expand Down Expand Up @@ -101,19 +139,28 @@ def keepalived():
t.start()

def cancel_keepalive(self, join=True):
"""
stop keeping-alive
:type join: bool
:param join: whether to wait the keepalive thread to exit
"""
self.keeping = False
if join and self._thread and self._thread.is_alive():
self._thread.join()

def jammed(self):
"""
if is failed to keepalive
if is failed to keepalive at the last loop
"""
if not self.keeping:
return False
return time.time() - self.last_keep > self.grantedTTL / 4.0

def revoke(self):
"""
revoke the lease
"""
log.debug("revoking lease %d" % self.ID)
self.cancel_keepalive(False)
return self.client.lease_revoke(self.ID)
Expand Down
39 changes: 27 additions & 12 deletions etcd3/stateful/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,8 @@ def __init__(self, client, lock_name, lock_ttl=DEFAULT_LOCK_TTL, reentrant=None,
:param lock_name: the name of the lock
:type lock_ttl: int
:param lock_ttl: ttl of the lock, default is 60s
:type host_global: bool
:param host_global: if set True, the uuid of the lock will be stored in a temp file
thus all the process on the host will hold the same lock
:type reentrant: str
:param reentrant: the reentrant type of the lock can set to Lock.HOST, Lock.PROCESS, Lock.THREAD
:type lock_prefix: str
:param lock_prefix: the prefix of the lock key
"""
Expand Down Expand Up @@ -72,6 +71,8 @@ def _get_uuid(self):
return '%s:thrd:%s' % (hostname, threading.get_ident())
elif self.reentrant == self.HOST:
return self._get_global_uuid('%s:host:%s' % (hostname, socket.gethostbyname(hostname)))
else:
raise TypeError("unknown reentrant type, expect one of Lock.HOST, Lock.PROCESS, Lock.THREAD")

def _get_global_uuid(self, uuid):
path = tempfile.gettempdir() + '/' + self.name + '_lock'
Expand Down Expand Up @@ -100,6 +101,11 @@ def _holders_lease(self):
return self.__holders_lease

def holders(self):
"""
tell how many holders are holding the lock
:return: int
"""
if not self.reentrant:
if self._get_locker():
return 1
Expand All @@ -118,6 +124,9 @@ def holders(self):
return 0

def incr_holder(self):
"""
Atomic increase the holder count by 1
"""
n = self.holders()
t = self.client.Txn()
t.If(t.key(self.holders_key).value == b'%d' % n)
Expand All @@ -128,6 +137,9 @@ def incr_holder(self):
log.debug("failed to incr holders count")

def decr_holder(self):
"""
Atomic decrease the holder count by 1
"""
n = self.holders() or 0
t = self.client.Txn()
t.If(t.key(self.holders_key).value == b'%d' % n)
Expand Down Expand Up @@ -223,8 +235,18 @@ def acquire(self, block=True, lock_ttl=None, timeout=None, delete_key=True):
log.debug("Lock acquired (lock_key: %s, value: %s)" % (self.lock_key, self.uuid))
return self

def refresh(self):
return self.acquire(block=False)
def wait(self, locker=None, timeout=None):
"""
Wait until the lock is lock is able to acquire
:param locker: kv of the lock
:param timeout: wait timeout
"""
locker = locker or self._get_locker()
if not locker:
return
self.watcher = watcher = self.client.Watcher(key=locker.key, max_retries=0)
return watcher.watch_once(lambda e: e.type == EventType.DELETE or e.value == self.uuid, timeout=timeout)

def release(self):
"""
Expand Down Expand Up @@ -256,10 +278,3 @@ def __enter__(self):
def __exit__(self, type, value, traceback):
self.release()
return False

def wait(self, locker=None, timeout=None):
locker = locker or self._get_locker()
if not locker:
return
self.watcher = watcher = self.client.Watcher(key=locker.key, max_retries=0)
return watcher.watch_once(lambda e: e.type == EventType.DELETE or e.value == self.uuid, timeout=timeout)

0 comments on commit 5df58ff

Please sign in to comment.