Skip to content

Commit

Permalink
refactor code, add extra apis
Browse files Browse the repository at this point in the history
  • Loading branch information
revol.cai committed Mar 5, 2018
1 parent d01aca9 commit 2b8f455
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 63 deletions.
2 changes: 2 additions & 0 deletions etcd3/aio_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def test_version_api(client):
print(client.version())
2 changes: 2 additions & 0 deletions etcd3/apis/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .auth import AuthAPI
from .base import BaseAPI
from .cluster import ClusterAPI
from .extra import ExtraAPI
from .kv import KVAPI
from .lease import LeaseAPI
from .maintenance import MaintenanceAPI
Expand All @@ -10,6 +11,7 @@
'WatchAPI',
'AuthAPI',
'ClusterAPI',
'ExtraAPI',
'KVAPI',
'MaintenanceAPI',
'LeaseAPI',
Expand Down
13 changes: 13 additions & 0 deletions etcd3/apis/base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
class BaseAPI(object):
@staticmethod
def _raise_for_status(resp):
raise NotImplementedError

def _url(self, method):
raise NotImplementedError

def _get(self, url, **kwargs):
raise NotImplementedError

def _post(self, url, data=None, json=None, **kwargs):
raise NotImplementedError

def call_rpc(self, method, data=None, stream=False, **kwargs):
raise NotImplementedError
33 changes: 33 additions & 0 deletions etcd3/apis/extra.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from collections import namedtuple

from .base import BaseAPI

EtcdVersion = namedtuple('EtcdVersion', ['etcdserver', 'etcdcluster'])


class ExtraAPI(BaseAPI):
def version(self):
"""
get the version of etcdserver and etcdcluster
:return: EtcdVersion
"""
resp = self._get(self._url('/version'))
self._raise_for_status(resp)
return EtcdVersion(**resp.json())

def metrics_raw(self):
"""
get the raw /metrics text
:return: str
"""
resp = self._get(self._url('/metrics'))
self._raise_for_status(resp)
return resp.content

def metrics(self):
"""
get the modelized metrics parsed by prometheus_client
"""
from prometheus_client.parser import text_string_to_metric_families

return text_string_to_metric_families(self.metrics_raw())
29 changes: 25 additions & 4 deletions etcd3/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from .apis import AuthAPI
from .apis import ClusterAPI
from .apis import ExtraAPI
from .apis import KVAPI
from .apis import LeaseAPI
from .apis import MaintenanceAPI
Expand Down Expand Up @@ -79,7 +80,7 @@ def iter_response(resp):
raise Etcd3StreamError("Stream decode error", buf, resp)


class Etcd3APIClient(AuthAPI, ClusterAPI, KVAPI, LeaseAPI, MaintenanceAPI, WatchAPI):
class Etcd3APIClient(AuthAPI, ClusterAPI, KVAPI, LeaseAPI, MaintenanceAPI, WatchAPI, ExtraAPI):
response_class = requests.models.Response

def __init__(self, host='localhost', port=2379, protocol='http',
Expand Down Expand Up @@ -158,7 +159,7 @@ def modelizeResponse(cls, method, resp, decode=True):
return respModel(resp)

@staticmethod
def raise_for_status(resp):
def _raise_for_status(resp):
status = resp.status_code
if status < 400:
return
Expand All @@ -172,6 +173,26 @@ def raise_for_status(resp):
code = data.get('code')
raise Etcd3APIError(error, code, status, resp)

def _get(self, url, **kwargs):
r"""Sends a GET request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
return self.session.get(url, **kwargs)

def _post(self, url, data=None, json=None, **kwargs):
r"""Sends a POST request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
:param json: (optional) json to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
return self.session.post(url, data=data, json=json, **kwargs)

def call_rpc(self, method, data=None, stream=False, encode=True, raw=False, **kwargs):
"""
call ETCDv3 RPC and return response object
Expand All @@ -193,8 +214,8 @@ def call_rpc(self, method, data=None, stream=False, encode=True, raw=False, **kw
kwargs.setdefault('headers', {}).update(self.headers)
if encode:
data = self.encodeRPCRequest(method, data)
resp = self.session.post(self._url(method), json=data or {}, stream=stream, **kwargs)
self.raise_for_status(resp)
resp = self._post(self._url(method), json=data or {}, stream=stream, **kwargs)
self._raise_for_status(resp)
if raw:
return resp
if stream:
Expand Down
82 changes: 41 additions & 41 deletions etcd3/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,28 @@ class AlarmRequestAlarmAction(enum.Enum):
DEACTIVATE = 'DEACTIVATE'


class CompareCompareResult(enum.Enum):
"""
ref: #/definitions/CompareCompareResult
default: EQUAL
"""
EQUAL = 'EQUAL'
GREATER = 'GREATER'
LESS = 'LESS'
NOT_EQUAL = 'NOT_EQUAL'


class CompareCompareTarget(enum.Enum):
"""
ref: #/definitions/CompareCompareTarget
default: VERSION
"""
VERSION = 'VERSION'
CREATE = 'CREATE'
MOD = 'MOD'
VALUE = 'VALUE'


class EventEventType(enum.Enum):
"""
ref: #/definitions/EventEventType
Expand All @@ -21,6 +43,16 @@ class EventEventType(enum.Enum):
DELETE = 'DELETE'


class RangeRequestSortOrder(enum.Enum):
"""
ref: #/definitions/RangeRequestSortOrder
default: NONE
"""
NONE = 'NONE'
ASCEND = 'ASCEND'
DESCEND = 'DESCEND'


class RangeRequestSortTarget(enum.Enum):
"""
ref: #/definitions/RangeRequestSortTarget
Expand All @@ -33,15 +65,13 @@ class RangeRequestSortTarget(enum.Enum):
VALUE = 'VALUE'


class CompareCompareTarget(enum.Enum):
class WatchCreateRequestFilterType(enum.Enum):
"""
ref: #/definitions/CompareCompareTarget
default: VERSION
ref: #/definitions/WatchCreateRequestFilterType
default: NOPUT
"""
VERSION = 'VERSION'
CREATE = 'CREATE'
MOD = 'MOD'
VALUE = 'VALUE'
NOPUT = 'NOPUT'
NODELETE = 'NODELETE'


class authpbPermissionType(enum.Enum):
Expand All @@ -54,27 +84,6 @@ class authpbPermissionType(enum.Enum):
READWRITE = 'READWRITE'


class CompareCompareResult(enum.Enum):
"""
ref: #/definitions/CompareCompareResult
default: EQUAL
"""
EQUAL = 'EQUAL'
GREATER = 'GREATER'
LESS = 'LESS'
NOT_EQUAL = 'NOT_EQUAL'


class RangeRequestSortOrder(enum.Enum):
"""
ref: #/definitions/RangeRequestSortOrder
default: NONE
"""
NONE = 'NONE'
ASCEND = 'ASCEND'
DESCEND = 'DESCEND'


class etcdserverpbAlarmType(enum.Enum):
"""
ref: #/definitions/etcdserverpbAlarmType
Expand All @@ -84,23 +93,14 @@ class etcdserverpbAlarmType(enum.Enum):
NOSPACE = 'NOSPACE'


class WatchCreateRequestFilterType(enum.Enum):
"""
ref: #/definitions/WatchCreateRequestFilterType
default: NOPUT
"""
NOPUT = 'NOPUT'
NODELETE = 'NODELETE'


__all__ = [
'AlarmRequestAlarmAction',
'CompareCompareResult',
'CompareCompareTarget',
'EventEventType',
'RangeRequestSortOrder',
'RangeRequestSortTarget',
'CompareCompareTarget',
'WatchCreateRequestFilterType',
'authpbPermissionType',
'CompareCompareResult',
'RangeRequestSortOrder',
'etcdserverpbAlarmType',
'WatchCreateRequestFilterType',
]
10 changes: 5 additions & 5 deletions etcd3/swagger_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import enum
import six

from .utils import lru_cache
from .utils import memoized

try:
import yaml
Expand Down Expand Up @@ -94,7 +94,7 @@ def construct_mapping(loader, node):
except Exception:
raise ValueError("Fail to load spec")

@lru_cache()
@memoized
def _ref(self, ref):
if not ref.startswith('#/'):
return None, None
Expand All @@ -116,7 +116,7 @@ def ref(self, ref_path):
def get(self, key, *args, **kwargs):
return self.spec.get(key, *args, **kwargs)

@lru_cache()
@memoized
def getPath(self, key):
"""
get a SwaggerPath instance of the path
Expand All @@ -132,7 +132,7 @@ def getPath(self, key):
node = self.ref(key)
return node

@lru_cache()
@memoized
def getSchema(self, key):
"""
get a SwaggerSchema instance of the schema
Expand Down Expand Up @@ -372,7 +372,7 @@ def _values(self):
def _items(self):
return six.iteritems(self._node)

@lru_cache()
@memoized
def __getattr__(self, key):
try:
original_key, n = _get_path(self._node, key)
Expand Down
36 changes: 35 additions & 1 deletion etcd3/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections import namedtuple, OrderedDict
import functools
from collections import namedtuple, OrderedDict, Hashable
from threading import Lock

import six
Expand Down Expand Up @@ -114,10 +115,43 @@ def cache_clear():
return decorating_function


class memoized(object):
'''Decorator. Caches a function's return value each time it is called.
If called later with the same arguments, the cached value is returned
(not reevaluated).
'''

def __init__(self, func):
self.func = func
self.cache = {}

def __call__(self, *args):
if not isinstance(args, Hashable):
# uncacheable. a list, for instance.
# better to not cache than blow up.
return self.func(*args)
if args in self.cache:
return self.cache[args]
else:
value = self.func(*args)
self.cache[args] = value
return value

def __repr__(self):
'''Return the function's docstring.'''
return self.func.__doc__

def __get__(self, obj, objtype):
'''Support instance methods.'''
return functools.partial(self.__call__, obj)


if __name__ == '__main__':
@lru_cache(maxsize=2)
def f(a):
print('calc:%s' % a)
return a ** 2


for i in range(1, 20):
print(f(i % 3))
12 changes: 8 additions & 4 deletions etcd3/extract_apis.py → scripts/extract_apis.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,20 @@ def {{ path.post.operationId | underscore}}(self,
{% endfor %}
'''

GENERATED_APIS_DIR = './apis_generated'

if __name__ == '__main__':
import os
import sys

sys.path.append(os.path.dirname(os.path.dirname(__file__)))

import jinja2
from swagger_helper import SwaggerSpec
from etcd3.swagger_helper import SwaggerSpec
from yapf.yapflib.yapf_api import FormatCode
from isort import SortImports

rpc_swagger_json = os.path.join(os.path.dirname(__file__), 'rpc.swagger.json')
GENERATED_APIS_DIR = os.path.join(os.path.dirname(__file__), '../etcd3/apis_generated')

rpc_swagger_json = os.path.join(os.path.dirname(__file__), '../etcd3/rpc.swagger.json')
swaggerSpec = SwaggerSpec(rpc_swagger_json)

api_tpl = env.from_string(API_FILE_TEMPLATE)
Expand Down

0 comments on commit 2b8f455

Please sign in to comment.