From 734a61a4aaf2f139f72c183cdcaf0a62416a19f9 Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 00:33:22 +0800 Subject: [PATCH 01/24] :beer: implement DiscoveryClient design --- commons/__init__.py | 0 commons/client/ServiceInstance.py | 90 ++++++ commons/client/__init__.py | 0 commons/client/discovery/__init__.py | 0 commons/client/discovery/composite.py | 23 ++ commons/client/discovery/discovery_client.py | 49 +++ commons/client/loadbalancer/__init__.py | 0 commons/client/service_registry/__init__.py | 0 commons/utils/__init__.py | 0 commons/utils/functional_operators.py | 13 + commons/utils/list_utils.py | 6 + context/__init__.py | 0 poetry.lock | 306 +++++++++++++++---- pyproject.toml | 2 + src/__init__.py | 13 - src/stub.py | 17 -- tests/__init__.py | 13 - tests/client/__init__.py | 0 tests/client/discovery/__init__.py | 0 tests/client/discovery/composite_test.py | 30 ++ tests/stub_test.py | 20 -- 21 files changed, 465 insertions(+), 117 deletions(-) create mode 100644 commons/__init__.py create mode 100644 commons/client/ServiceInstance.py create mode 100644 commons/client/__init__.py create mode 100644 commons/client/discovery/__init__.py create mode 100644 commons/client/discovery/composite.py create mode 100644 commons/client/discovery/discovery_client.py create mode 100644 commons/client/loadbalancer/__init__.py create mode 100644 commons/client/service_registry/__init__.py create mode 100644 commons/utils/__init__.py create mode 100644 commons/utils/functional_operators.py create mode 100644 commons/utils/list_utils.py create mode 100644 context/__init__.py delete mode 100644 src/__init__.py delete mode 100644 src/stub.py create mode 100644 tests/client/__init__.py create mode 100644 tests/client/discovery/__init__.py create mode 100644 tests/client/discovery/composite_test.py delete mode 100644 tests/stub_test.py diff --git a/commons/__init__.py b/commons/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/commons/client/ServiceInstance.py b/commons/client/ServiceInstance.py new file mode 100644 index 0000000..c716605 --- /dev/null +++ b/commons/client/ServiceInstance.py @@ -0,0 +1,90 @@ +# standard library +from abc import ABC, abstractmethod +from urllib.parse import urlparse + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +class ServiceInstance(ABC): + @property + @abstractmethod + def instance_id(self): + pass + + @property + @abstractmethod + def service_id(self): + pass + + @property + @abstractmethod + def host(self): + pass + + @property + @abstractmethod + def port(self): + pass + + @property + @abstractmethod + def secure(self): + pass + + @property + @abstractmethod + def uri(self): + pass + + @property + @abstractmethod + def scheme(self): + pass + + +class StaticServiceInstance(ServiceInstance): + """ + A service instance that is initialized with its basic properties + """ + + def __init__(self, uri, service_id, instance_id): + """ + :param uri: the url in the string type + """ + url_obj = urlparse(uri) + self._uri = uri + self._scheme = url_obj.scheme + self._secure = self._scheme == "https" + self._host = url_obj.netloc + self._port = url_obj.port + self._service_id = service_id + self._instance_id = instance_id + + @property + def service_id(self): + return self._service_id + + @property + def instance_id(self): + return self._instance_id + + @property + def host(self): + return self._host + + @property + def port(self): + return self._port + + @property + def secure(self): + return self._secure + + @property + def uri(self): + return self._uri + + @property + def scheme(self): + return self._scheme diff --git a/commons/client/__init__.py b/commons/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/commons/client/discovery/__init__.py b/commons/client/discovery/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/commons/client/discovery/composite.py b/commons/client/discovery/composite.py new file mode 100644 index 0000000..cb39845 --- /dev/null +++ b/commons/client/discovery/composite.py @@ -0,0 +1,23 @@ +# scip plugin +from commons.client.discovery.discovery_client import DiscoveryClient +from commons.utils.functional_operators import flat_map +from commons.utils.list_utils import not_null_nor_empty + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +class CompositeDiscoveryClient(DiscoveryClient): + def __init__(self, *discovery_clients): + self.discovery_clients = discovery_clients + + def get_instances(self, service_id): + for client in self.discovery_clients: + services = client.get_instances(service_id) + if not_null_nor_empty(services): + return services + return [] + + @property + def services(self): + return flat_map(lambda d: d.services, self.discovery_clients) diff --git a/commons/client/discovery/discovery_client.py b/commons/client/discovery/discovery_client.py new file mode 100644 index 0000000..a3bca2e --- /dev/null +++ b/commons/client/discovery/discovery_client.py @@ -0,0 +1,49 @@ +# standard library +from abc import ABC, abstractmethod + +# scip plugin +from commons.client.ServiceInstance import StaticServiceInstance +from commons.utils.functional_operators import filter_get_first + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +class DiscoveryClient(ABC): + @abstractmethod + def get_instances(self, service_id): + """ + Gets all ServiceInstances associated with a particular serviceId. + :param service_id: The serviceId to query. + :return: A List of ServiceInstance. + """ + pass + + @property + @abstractmethod + def services(self): + """ + :return: All known service IDs. + """ + pass + + +class StaticDiscoveryClient(DiscoveryClient): + """ + A DiscoveryClient initialized with the services + """ + + def __init__(self, services): + self._services = services + + def get_instances(self, service_id): + return list(filter(lambda s: s.service_id == service_id, self.services)) + + @property + def services(self): + return self._services + + +def static_discovery_client(uri, service_id, instance_ids): + services = [StaticServiceInstance(uri, service_id, instance_id) for instance_id in instance_ids] + return StaticDiscoveryClient(services) diff --git a/commons/client/loadbalancer/__init__.py b/commons/client/loadbalancer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/commons/client/service_registry/__init__.py b/commons/client/service_registry/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/commons/utils/__init__.py b/commons/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/commons/utils/functional_operators.py b/commons/utils/functional_operators.py new file mode 100644 index 0000000..013b851 --- /dev/null +++ b/commons/utils/functional_operators.py @@ -0,0 +1,13 @@ +# standard library +from functools import reduce + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +def flat_map(f, xs): + return reduce(lambda a, b: a + b, map(f, xs)) + + +def filter_get_first(f, the_list): + return next(filter(f, the_list), None) diff --git a/commons/utils/list_utils.py b/commons/utils/list_utils.py new file mode 100644 index 0000000..6d403ff --- /dev/null +++ b/commons/utils/list_utils.py @@ -0,0 +1,6 @@ +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +def not_null_nor_empty(the_list): + return the_list is not None and len(the_list) != 0 diff --git a/context/__init__.py b/context/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/poetry.lock b/poetry.lock index c8880eb..181b4e7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,54 +1,94 @@ [[package]] -category = "dev" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = "*" -version = "1.4.4" [[package]] -category = "dev" -description = "Validate configuration and produce human readable error messages." +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "20.3.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +docs = ["furo", "sphinx", "zope.interface"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] + +[[package]] name = "cfgv" +version = "3.2.0" +description = "Validate configuration and produce human readable error messages." +category = "dev" optional = false python-versions = ">=3.6.1" -version = "3.2.0" [[package]] -category = "dev" -description = "Distribution utilities" +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "coverage" +version = "5.3" +description = "Code coverage measurement for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +toml = ["toml"] + +[[package]] name = "distlib" +version = "0.3.1" +description = "Distribution utilities" +category = "dev" optional = false python-versions = "*" -version = "0.3.1" [[package]] -category = "dev" -description = "A platform independent file lock." name = "filelock" +version = "3.0.12" +description = "A platform independent file lock." +category = "dev" optional = false python-versions = "*" -version = "3.0.12" [[package]] -category = "dev" -description = "File identification library for Python" name = "identify" +version = "1.5.5" +description = "File identification library for Python" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "1.5.5" [package.extras] license = ["editdistance"] [[package]] -category = "dev" -description = "Read metadata from Python packages" -marker = "python_version < \"3.8\"" name = "importlib-metadata" +version = "2.0.0" +description = "Read metadata from Python packages" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "2.0.0" [package.dependencies] zipp = ">=0.5" @@ -58,105 +98,235 @@ docs = ["sphinx", "rst.linker"] testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] [[package]] -category = "dev" -description = "Node.js virtual environment builder" -name = "nodeenv" +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "main" optional = false python-versions = "*" -version = "1.5.0" [[package]] +name = "nodeenv" +version = "1.5.0" +description = "Node.js virtual environment builder" category = "dev" -description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "20.4" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pyparsing = ">=2.0.2" +six = "*" + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] name = "pre-commit" +version = "2.7.1" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" optional = false python-versions = ">=3.6.1" -version = "2.7.1" [package.dependencies] +virtualenv = ">=20.0.8" +pyyaml = ">=5.1" cfgv = ">=2.0.0" -identify = ">=1.0.0" nodeenv = ">=0.11.1" -pyyaml = ">=5.1" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +identify = ">=1.0.0" toml = "*" -virtualenv = ">=20.0.8" -[package.dependencies.importlib-metadata] -python = "<3.8" -version = "*" +[[package]] +name = "py" +version = "1.9.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] -category = "dev" -description = "YAML parser and emitter for Python" -name = "pyyaml" +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pytest" +version = "6.1.2" +description = "pytest: simple powerful testing with Python" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +packaging = "*" +py = ">=1.8.2" +iniconfig = "*" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +colorama = {version = "*", markers = "sys_platform == \"win32\""} +attrs = ">=17.4.0" +toml = "*" +pluggy = ">=0.12,<1.0" +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +checkqa_mypy = ["mypy (==0.780)"] + +[[package]] +name = "pytest-cov" +version = "2.10.1" +description = "Pytest plugin for measuring coverage." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "5.3.1" + +[package.dependencies] +pytest = ">=4.6" +coverage = ">=4.4" + +[package.extras] +testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] [[package]] +name = "pyyaml" +version = "5.3.1" +description = "YAML parser and emitter for Python" category = "dev" -description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] name = "six" +version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.15.0" [[package]] -category = "dev" -description = "Python Library for Tom's Obvious, Minimal Language" name = "toml" +version = "0.10.1" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "main" optional = false python-versions = "*" -version = "0.10.1" [[package]] -category = "dev" -description = "Virtual Python Environment builder" name = "virtualenv" +version = "20.0.33" +description = "Virtual Python Environment builder" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "20.0.33" [package.dependencies] -appdirs = ">=1.4.3,<2" +importlib-metadata = {version = ">=0.12,<3", markers = "python_version < \"3.8\""} distlib = ">=0.3.1,<1" filelock = ">=3.0.0,<4" six = ">=1.9.0,<2" - -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12,<3" +appdirs = ">=1.4.3,<2" [package.extras] docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "pytest-xdist (>=1.31.0)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] [[package]] -category = "dev" -description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version < \"3.8\"" name = "zipp" +version = "3.3.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" optional = false python-versions = ">=3.6" -version = "3.3.0" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] -content-hash = "6082dab94ba09f8cac3bf2c2a65ed2ed32c8b385ce34e2a81e683f325ed55e13" +lock-version = "1.1" python-versions = "^3.7" +content-hash = "1789c0c6dfba8dea8f6c09917f6f30aad10173e904761db257b0dd790dfb5543" [metadata.files] appdirs = [ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, + {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, +] cfgv = [ {file = "cfgv-3.2.0-py2.py3-none-any.whl", hash = "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d"}, {file = "cfgv-3.2.0.tar.gz", hash = "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1"}, ] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +coverage = [ + {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"}, + {file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"}, + {file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"}, + {file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"}, + {file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"}, + {file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"}, + {file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"}, + {file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"}, + {file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"}, + {file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"}, + {file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"}, + {file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"}, + {file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"}, + {file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"}, + {file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"}, + {file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"}, + {file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"}, + {file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"}, + {file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"}, + {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"}, +] distlib = [ {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"}, @@ -173,14 +343,42 @@ importlib-metadata = [ {file = "importlib_metadata-2.0.0-py2.py3-none-any.whl", hash = "sha256:cefa1a2f919b866c5beb7c9f7b0ebb4061f30a8a9bf16d609b000e2dfaceb9c3"}, {file = "importlib_metadata-2.0.0.tar.gz", hash = "sha256:77a540690e24b0305878c37ffd421785a6f7e53c8b5720d211b211de8d0e95da"}, ] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] nodeenv = [ {file = "nodeenv-1.5.0-py2.py3-none-any.whl", hash = "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9"}, {file = "nodeenv-1.5.0.tar.gz", hash = "sha256:ab45090ae383b716c4ef89e690c41ff8c2b257b85b309f01f3654df3d084bd7c"}, ] +packaging = [ + {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, + {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] pre-commit = [ {file = "pre_commit-2.7.1-py2.py3-none-any.whl", hash = "sha256:810aef2a2ba4f31eed1941fc270e72696a1ad5590b9751839c90807d0fff6b9a"}, {file = "pre_commit-2.7.1.tar.gz", hash = "sha256:c54fd3e574565fe128ecc5e7d2f91279772ddb03f8729645fa812fe809084a70"}, ] +py = [ + {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, + {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-6.1.2-py3-none-any.whl", hash = "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe"}, + {file = "pytest-6.1.2.tar.gz", hash = "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"}, +] +pytest-cov = [ + {file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"}, + {file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"}, +] pyyaml = [ {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, diff --git a/pyproject.toml b/pyproject.toml index 0594a6b..e642ff9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,8 @@ license = "Apache-2.0" [tool.poetry.dependencies] python = "^3.7" +pytest = "^6.1.2" +pytest-cov = "^2.10.1" [tool.poetry.dev-dependencies] pre-commit = "^2.7.1" diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index b9b3c0c..0000000 --- a/src/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2020 Waterball (johnny850807@gmail.com) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/src/stub.py b/src/stub.py deleted file mode 100644 index 56d99ca..0000000 --- a/src/stub.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2020 Waterball (johnny850807@gmail.com) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -def add(a, b): - return a + b diff --git a/tests/__init__.py b/tests/__init__.py index b9b3c0c..e69de29 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,13 +0,0 @@ -# Copyright 2020 Waterball (johnny850807@gmail.com) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tests/client/__init__.py b/tests/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/client/discovery/__init__.py b/tests/client/discovery/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/client/discovery/composite_test.py b/tests/client/discovery/composite_test.py new file mode 100644 index 0000000..39a4990 --- /dev/null +++ b/tests/client/discovery/composite_test.py @@ -0,0 +1,30 @@ +# scip plugin +from commons.client.discovery.composite import CompositeDiscoveryClient +from commons.client.discovery.discovery_client import static_discovery_client + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache" + +client = CompositeDiscoveryClient( + static_discovery_client("url-1", "service-1", ["1-1", "1-2", "1-3"]), + static_discovery_client("url-2", "service-2", ["2-1", "2-2", "2-3"]), + static_discovery_client("url-3", "service-3", ["3-1", "3-2", "3-3"]), +) + + +def test_get_instances(): + for service_id in range(1, 4): + instances = client.get_instances("service-{}".format(service_id)) + for instance_id in range(1, 4): + instance = instances[instance_id - 1] + assert instance.instance_id == "{}-{}".format(service_id, instance_id) + + +def test_get_services(): + instances = client.services + i = 0 + for service_id in range(1, 4): + for instance_id in range(1, 4): + assert instances[i].service_id == "service-{}".format(service_id) + assert instances[i].instance_id == "{}-{}".format(service_id, instance_id) + i += 1 diff --git a/tests/stub_test.py b/tests/stub_test.py deleted file mode 100644 index de90136..0000000 --- a/tests/stub_test.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2020 Waterball (johnny850807@gmail.com) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# scip plugin -from src import stub - - -def test(): - assert 10 == stub.add(4, 6) From a94c3b3d41b661a615da3bf0c20e668494f954cd Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 01:06:38 +0800 Subject: [PATCH 02/24] Add more comments --- commons/client/discovery/composite.py | 4 ++++ commons/client/discovery/discovery_client.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/commons/client/discovery/composite.py b/commons/client/discovery/composite.py index cb39845..ddc3432 100644 --- a/commons/client/discovery/composite.py +++ b/commons/client/discovery/composite.py @@ -8,6 +8,10 @@ class CompositeDiscoveryClient(DiscoveryClient): + """ + Composite pattern application: aggregate the service sources from a list of discovery client + """ + def __init__(self, *discovery_clients): self.discovery_clients = discovery_clients diff --git a/commons/client/discovery/discovery_client.py b/commons/client/discovery/discovery_client.py index a3bca2e..c5fb607 100644 --- a/commons/client/discovery/discovery_client.py +++ b/commons/client/discovery/discovery_client.py @@ -45,5 +45,9 @@ def services(self): def static_discovery_client(uri, service_id, instance_ids): + """ + Usage: + static_discovery_client("url-1", "service-1", ["id-1", "id-2", "id-3"]) + """ services = [StaticServiceInstance(uri, service_id, instance_id) for instance_id in instance_ids] return StaticDiscoveryClient(services) From fc2dbb4ee569ee7257ab1064d6b1340ae301e7f1 Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 11:32:19 +0800 Subject: [PATCH 03/24] Modify some functional operation concerning performance --- README.md | 6 +++++ commons/client/ServiceInstance.py | 4 +++ commons/client/discovery/composite.py | 4 +-- commons/utils/functional_operators.py | 7 ++++-- commons/utils/list_utils.py | 7 +++++- poetry.lock | 32 ++++++++++++------------ pyproject.toml | 4 +-- tests/client/discovery/composite_test.py | 6 ++--- tests/utils/__init__.py | 2 ++ tests/utils/functional_operators_test.py | 17 +++++++++++++ 10 files changed, 63 insertions(+), 26 deletions(-) create mode 100644 tests/utils/__init__.py create mode 100644 tests/utils/functional_operators_test.py diff --git a/README.md b/README.md index 36e8225..bf1e281 100644 --- a/README.md +++ b/README.md @@ -49,3 +49,9 @@ poetry run pre-commit install ``` 6. Start to commit :) + +## Test + +1. Show test coverage report on terminal + +`poetry run pytest --cov-report term --cov ${package} tests/` diff --git a/commons/client/ServiceInstance.py b/commons/client/ServiceInstance.py index c716605..0edc3ba 100644 --- a/commons/client/ServiceInstance.py +++ b/commons/client/ServiceInstance.py @@ -7,6 +7,10 @@ class ServiceInstance(ABC): + """ + + """ + @property @abstractmethod def instance_id(self): diff --git a/commons/client/discovery/composite.py b/commons/client/discovery/composite.py index ddc3432..c3be2fa 100644 --- a/commons/client/discovery/composite.py +++ b/commons/client/discovery/composite.py @@ -1,7 +1,7 @@ # scip plugin from commons.client.discovery.discovery_client import DiscoveryClient from commons.utils.functional_operators import flat_map -from commons.utils.list_utils import not_null_nor_empty +from commons.utils.list_utils import not_none_nor_empty __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" @@ -18,7 +18,7 @@ def __init__(self, *discovery_clients): def get_instances(self, service_id): for client in self.discovery_clients: services = client.get_instances(service_id) - if not_null_nor_empty(services): + if not_none_nor_empty(services): return services return [] diff --git a/commons/utils/functional_operators.py b/commons/utils/functional_operators.py index 013b851..54463f6 100644 --- a/commons/utils/functional_operators.py +++ b/commons/utils/functional_operators.py @@ -6,8 +6,11 @@ def flat_map(f, xs): - return reduce(lambda a, b: a + b, map(f, xs)) + results = [] + for element in xs: + results += f(element) + return results def filter_get_first(f, the_list): - return next(filter(f, the_list), None) + return [x for x in the_list if f(x)][0] diff --git a/commons/utils/list_utils.py b/commons/utils/list_utils.py index 6d403ff..8e2c8e3 100644 --- a/commons/utils/list_utils.py +++ b/commons/utils/list_utils.py @@ -2,5 +2,10 @@ __license__ = "Apache 2.0" -def not_null_nor_empty(the_list): +def not_none_nor_empty(the_list): + """ + :param the_list: a list + :return: true if the list is neither none nor empty + """ + assert not the_list or isinstance(the_list, list) return the_list is not None and len(the_list) != 0 diff --git a/poetry.lock b/poetry.lock index 181b4e7..26d957a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -10,7 +10,7 @@ python-versions = "*" name = "atomicwrites" version = "1.4.0" description = "Atomic file writes." -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -18,7 +18,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "attrs" version = "20.3.0" description = "Classes Without Boilerplate" -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -40,7 +40,7 @@ python-versions = ">=3.6.1" name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -48,7 +48,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" name = "coverage" version = "5.3" description = "Code coverage measurement for Python" -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" @@ -86,7 +86,7 @@ license = ["editdistance"] name = "importlib-metadata" version = "2.0.0" description = "Read metadata from Python packages" -category = "main" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" @@ -101,7 +101,7 @@ testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" -category = "main" +category = "dev" optional = false python-versions = "*" @@ -117,7 +117,7 @@ python-versions = "*" name = "packaging" version = "20.4" description = "Core utilities for Python packages" -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -129,7 +129,7 @@ six = "*" name = "pluggy" version = "0.13.1" description = "plugin and hook calling mechanisms for python" -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -160,7 +160,7 @@ toml = "*" name = "py" version = "1.9.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -168,7 +168,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "pyparsing" version = "2.4.7" description = "Python parsing module" -category = "main" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" @@ -176,7 +176,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" name = "pytest" version = "6.1.2" description = "pytest: simple powerful testing with Python" -category = "main" +category = "dev" optional = false python-versions = ">=3.5" @@ -199,7 +199,7 @@ checkqa_mypy = ["mypy (==0.780)"] name = "pytest-cov" version = "2.10.1" description = "Pytest plugin for measuring coverage." -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -222,7 +222,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" name = "six" version = "1.15.0" description = "Python 2 and 3 compatibility utilities" -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" @@ -230,7 +230,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" name = "toml" version = "0.10.1" description = "Python Library for Tom's Obvious, Minimal Language" -category = "main" +category = "dev" optional = false python-versions = "*" @@ -257,7 +257,7 @@ testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", name = "zipp" version = "3.3.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -268,7 +268,7 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "1789c0c6dfba8dea8f6c09917f6f30aad10173e904761db257b0dd790dfb5543" +content-hash = "190192c42431e322402cdda9bf4820d92afcffbaca203bb403e552118ff6eb7c" [metadata.files] appdirs = [ diff --git a/pyproject.toml b/pyproject.toml index e642ff9..de09a11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,11 +7,11 @@ license = "Apache-2.0" [tool.poetry.dependencies] python = "^3.7" -pytest = "^6.1.2" -pytest-cov = "^2.10.1" [tool.poetry.dev-dependencies] pre-commit = "^2.7.1" +pytest = "^6.1.2" +pytest-cov = "^2.10.1" [build-system] requires = ["poetry>=0.12"] diff --git a/tests/client/discovery/composite_test.py b/tests/client/discovery/composite_test.py index 39a4990..758c4e3 100644 --- a/tests/client/discovery/composite_test.py +++ b/tests/client/discovery/composite_test.py @@ -17,7 +17,7 @@ def test_get_instances(): instances = client.get_instances("service-{}".format(service_id)) for instance_id in range(1, 4): instance = instances[instance_id - 1] - assert instance.instance_id == "{}-{}".format(service_id, instance_id) + assert "{}-{}".format(service_id, instance_id) == instance.instance_id def test_get_services(): @@ -25,6 +25,6 @@ def test_get_services(): i = 0 for service_id in range(1, 4): for instance_id in range(1, 4): - assert instances[i].service_id == "service-{}".format(service_id) - assert instances[i].instance_id == "{}-{}".format(service_id, instance_id) + assert "service-{}".format(service_id) == instances[i].service_id + assert "{}-{}".format(service_id, instance_id) == instances[i].instance_id i += 1 diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py new file mode 100644 index 0000000..d7b1237 --- /dev/null +++ b/tests/utils/__init__.py @@ -0,0 +1,2 @@ +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" diff --git a/tests/utils/functional_operators_test.py b/tests/utils/functional_operators_test.py new file mode 100644 index 0000000..cf40349 --- /dev/null +++ b/tests/utils/functional_operators_test.py @@ -0,0 +1,17 @@ +# scip plugin +from commons.utils.functional_operators import filter_get_first, flat_map + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +def test_flat_map(): + the_list = ["a,b,c", "1,2,3", ""] + results = flat_map(lambda e: e.split(","), the_list) + assert ["a", "b", "c", "1", "2", "3", ""] == results + + +def test_filter_get_first(): + the_list = [1, 2, 3, 4, 5, 6, 7, 8] + result = filter_get_first(lambda e: e > 4, the_list) + assert 5 == result From 02c1d470bcf74f2c5aac9e9287307fa744c748f5 Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 11:54:22 +0800 Subject: [PATCH 04/24] :memo: add typing and docstrings --- commons/__init__.py | 1 + commons/client/ServiceInstance.py | 11 +++++++---- commons/client/__init__.py | 1 + commons/client/discovery/__init__.py | 1 + commons/client/discovery/composite.py | 14 ++++++++++---- commons/client/discovery/discovery_client.py | 18 +++++++++++++++--- commons/client/loadbalancer/__init__.py | 1 + commons/client/service_registry/__init__.py | 1 + commons/utils/__init__.py | 1 + commons/utils/functional_operators.py | 17 +++++++++++++---- commons/utils/list_utils.py | 2 ++ context/__init__.py | 1 + tests/__init__.py | 1 + tests/client/__init__.py | 1 + tests/client/discovery/__init__.py | 1 + tests/client/discovery/composite_test.py | 8 +++++--- 16 files changed, 62 insertions(+), 18 deletions(-) diff --git a/commons/__init__.py b/commons/__init__.py index e69de29..40a96af 100644 --- a/commons/__init__.py +++ b/commons/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/commons/client/ServiceInstance.py b/commons/client/ServiceInstance.py index 0edc3ba..1d2f701 100644 --- a/commons/client/ServiceInstance.py +++ b/commons/client/ServiceInstance.py @@ -1,14 +1,17 @@ -# standard library -from abc import ABC, abstractmethod -from urllib.parse import urlparse +# -*- coding: utf-8 -*- __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" +# standard library +from abc import ABC, abstractmethod +from urllib.parse import urlparse + + class ServiceInstance(ABC): """ - + A service's instance that owns basic HTTP info. """ @property diff --git a/commons/client/__init__.py b/commons/client/__init__.py index e69de29..40a96af 100644 --- a/commons/client/__init__.py +++ b/commons/client/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/commons/client/discovery/__init__.py b/commons/client/discovery/__init__.py index e69de29..40a96af 100644 --- a/commons/client/discovery/__init__.py +++ b/commons/client/discovery/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/commons/client/discovery/composite.py b/commons/client/discovery/composite.py index c3be2fa..7bc38b0 100644 --- a/commons/client/discovery/composite.py +++ b/commons/client/discovery/composite.py @@ -1,18 +1,24 @@ +# -*- coding: utf-8 -*- + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + # scip plugin from commons.client.discovery.discovery_client import DiscoveryClient from commons.utils.functional_operators import flat_map from commons.utils.list_utils import not_none_nor_empty -__author__ = "Waterball (johnny850807@gmail.com)" -__license__ = "Apache 2.0" - class CompositeDiscoveryClient(DiscoveryClient): """ - Composite pattern application: aggregate the service sources from a list of discovery client + Composite pattern application: + aggregate the service sources from a list of discovery client """ def __init__(self, *discovery_clients): + """ + :param discovery_clients: a list of DiscoveryClient + """ self.discovery_clients = discovery_clients def get_instances(self, service_id): diff --git a/commons/client/discovery/discovery_client.py b/commons/client/discovery/discovery_client.py index c5fb607..6606b9e 100644 --- a/commons/client/discovery/discovery_client.py +++ b/commons/client/discovery/discovery_client.py @@ -1,3 +1,11 @@ +# -*- coding: utf-8 -*- +""" +DiscoveryClient is responsible for providing a list of service's instances. +There may be many services, for example: 'user-service', 'order-service', 'product-service', and so on. +Where each service may have several instances registered (e.g. user-service-1, user-service-2, ...), +and the registered instances are called ServiceInstances. +""" + # standard library from abc import ABC, abstractmethod @@ -14,7 +22,7 @@ class DiscoveryClient(ABC): def get_instances(self, service_id): """ Gets all ServiceInstances associated with a particular serviceId. - :param service_id: The serviceId to query. + :param service_id: (str) The serviceId to query. :return: A List of ServiceInstance. """ pass @@ -23,14 +31,14 @@ def get_instances(self, service_id): @abstractmethod def services(self): """ - :return: All known service IDs. + :return: All known service IDs (*str). """ pass class StaticDiscoveryClient(DiscoveryClient): """ - A DiscoveryClient initialized with the services + A DiscoveryClient initialized with the services. """ def __init__(self, services): @@ -46,6 +54,10 @@ def services(self): def static_discovery_client(uri, service_id, instance_ids): """ + A helper method that helps create a list of instances from the same service. + :param uri the uri of every service's + :param service_id the service_id of the service + :param instance_ids a list of ids (*str) of the instances' Usage: static_discovery_client("url-1", "service-1", ["id-1", "id-2", "id-3"]) """ diff --git a/commons/client/loadbalancer/__init__.py b/commons/client/loadbalancer/__init__.py index e69de29..40a96af 100644 --- a/commons/client/loadbalancer/__init__.py +++ b/commons/client/loadbalancer/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/commons/client/service_registry/__init__.py b/commons/client/service_registry/__init__.py index e69de29..40a96af 100644 --- a/commons/client/service_registry/__init__.py +++ b/commons/client/service_registry/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/commons/utils/__init__.py b/commons/utils/__init__.py index e69de29..40a96af 100644 --- a/commons/utils/__init__.py +++ b/commons/utils/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/commons/utils/functional_operators.py b/commons/utils/functional_operators.py index 54463f6..084fb45 100644 --- a/commons/utils/functional_operators.py +++ b/commons/utils/functional_operators.py @@ -1,16 +1,25 @@ -# standard library -from functools import reduce +# -*- coding: utf-8 -*- __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" def flat_map(f, xs): + """ + :param f: a mapping function + :param xs: (Iterable) + :return: list + """ results = [] for element in xs: results += f(element) return results -def filter_get_first(f, the_list): - return [x for x in the_list if f(x)][0] +def filter_get_first(f, xs): + """ + :param f: a mapping function + :param xs: (Iterable) + :return: an element + """ + return [x for x in xs if f(x)][0] diff --git a/commons/utils/list_utils.py b/commons/utils/list_utils.py index 8e2c8e3..5185483 100644 --- a/commons/utils/list_utils.py +++ b/commons/utils/list_utils.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" diff --git a/context/__init__.py b/context/__init__.py index e69de29..40a96af 100644 --- a/context/__init__.py +++ b/context/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..40a96af 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/tests/client/__init__.py b/tests/client/__init__.py index e69de29..40a96af 100644 --- a/tests/client/__init__.py +++ b/tests/client/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/tests/client/discovery/__init__.py b/tests/client/discovery/__init__.py index e69de29..40a96af 100644 --- a/tests/client/discovery/__init__.py +++ b/tests/client/discovery/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/tests/client/discovery/composite_test.py b/tests/client/discovery/composite_test.py index 758c4e3..f9eb46d 100644 --- a/tests/client/discovery/composite_test.py +++ b/tests/client/discovery/composite_test.py @@ -1,10 +1,12 @@ +# -*- coding: utf-8 -*- + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + # scip plugin from commons.client.discovery.composite import CompositeDiscoveryClient from commons.client.discovery.discovery_client import static_discovery_client -__author__ = "Waterball (johnny850807@gmail.com)" -__license__ = "Apache" - client = CompositeDiscoveryClient( static_discovery_client("url-1", "service-1", ["1-1", "1-2", "1-3"]), static_discovery_client("url-2", "service-2", ["2-1", "2-2", "2-3"]), From 617c03eefe2a53a363fc2f89a55caaee817571c5 Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 12:31:09 +0800 Subject: [PATCH 05/24] Make some fields private --- commons/client/discovery/composite.py | 6 +++--- commons/client/discovery/discovery_client.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/commons/client/discovery/composite.py b/commons/client/discovery/composite.py index 7bc38b0..bd4ecb2 100644 --- a/commons/client/discovery/composite.py +++ b/commons/client/discovery/composite.py @@ -19,10 +19,10 @@ def __init__(self, *discovery_clients): """ :param discovery_clients: a list of DiscoveryClient """ - self.discovery_clients = discovery_clients + self.__discovery_clients = discovery_clients def get_instances(self, service_id): - for client in self.discovery_clients: + for client in self.__discovery_clients: services = client.get_instances(service_id) if not_none_nor_empty(services): return services @@ -30,4 +30,4 @@ def get_instances(self, service_id): @property def services(self): - return flat_map(lambda d: d.services, self.discovery_clients) + return flat_map(lambda d: d.services, self.__discovery_clients) diff --git a/commons/client/discovery/discovery_client.py b/commons/client/discovery/discovery_client.py index 6606b9e..f013fc7 100644 --- a/commons/client/discovery/discovery_client.py +++ b/commons/client/discovery/discovery_client.py @@ -42,14 +42,14 @@ class StaticDiscoveryClient(DiscoveryClient): """ def __init__(self, services): - self._services = services + self.__services = services def get_instances(self, service_id): return list(filter(lambda s: s.service_id == service_id, self.services)) @property def services(self): - return self._services + return self.__services def static_discovery_client(uri, service_id, instance_ids): From cdfa9a726b5ef5607d090be8038cf48f2ea09ea4 Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 12:32:27 +0800 Subject: [PATCH 06/24] Fix naming --- commons/client/discovery/discovery_client.py | 2 +- commons/client/{ServiceInstance.py => service_instance.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename commons/client/{ServiceInstance.py => service_instance.py} (100%) diff --git a/commons/client/discovery/discovery_client.py b/commons/client/discovery/discovery_client.py index f013fc7..dc94e94 100644 --- a/commons/client/discovery/discovery_client.py +++ b/commons/client/discovery/discovery_client.py @@ -10,7 +10,7 @@ from abc import ABC, abstractmethod # scip plugin -from commons.client.ServiceInstance import StaticServiceInstance +from commons.client.service_instance import StaticServiceInstance from commons.utils.functional_operators import filter_get_first __author__ = "Waterball (johnny850807@gmail.com)" diff --git a/commons/client/ServiceInstance.py b/commons/client/service_instance.py similarity index 100% rename from commons/client/ServiceInstance.py rename to commons/client/service_instance.py From 70c8aa1867d7524ec364088edf38a15de901cb45 Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 14:57:32 +0800 Subject: [PATCH 07/24] pending discussion: next+filter vs list comprehension --- commons/utils/functional_operators.py | 6 +++--- commons/utils/list_utils.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/commons/utils/functional_operators.py b/commons/utils/functional_operators.py index 084fb45..386d3da 100644 --- a/commons/utils/functional_operators.py +++ b/commons/utils/functional_operators.py @@ -18,8 +18,8 @@ def flat_map(f, xs): def filter_get_first(f, xs): """ - :param f: a mapping function - :param xs: (Iterable) + :param f: a predicate function that returns a boolean + :param xs: (Iterable :return: an element """ - return [x for x in xs if f(x)][0] + return next(filter(f, xs), None) diff --git a/commons/utils/list_utils.py b/commons/utils/list_utils.py index 5185483..044008d 100644 --- a/commons/utils/list_utils.py +++ b/commons/utils/list_utils.py @@ -9,5 +9,4 @@ def not_none_nor_empty(the_list): :param the_list: a list :return: true if the list is neither none nor empty """ - assert not the_list or isinstance(the_list, list) return the_list is not None and len(the_list) != 0 From 41a8c648439aece6803d950ec53bcb73507fed13 Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 20:22:58 +0800 Subject: [PATCH 08/24] :art: Add type checking syntax --- commons/client/discovery/composite.py | 16 ++++++----- commons/client/discovery/discovery_client.py | 21 +++++++------- commons/client/service_instance.py | 30 ++++++++++---------- commons/utils/functional_operators.py | 17 ++++++----- commons/utils/list_utils.py | 3 +- tests/client/discovery/composite_test.py | 13 +++------ 6 files changed, 48 insertions(+), 52 deletions(-) diff --git a/commons/client/discovery/composite.py b/commons/client/discovery/composite.py index bd4ecb2..5541ec0 100644 --- a/commons/client/discovery/composite.py +++ b/commons/client/discovery/composite.py @@ -1,4 +1,9 @@ # -*- coding: utf-8 -*- +# standard library +from typing import List, Set + +# scip plugin +from commons.client.service_instance import ServiceInstance __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" @@ -15,13 +20,10 @@ class CompositeDiscoveryClient(DiscoveryClient): aggregate the service sources from a list of discovery client """ - def __init__(self, *discovery_clients): - """ - :param discovery_clients: a list of DiscoveryClient - """ + def __init__(self, *discovery_clients: DiscoveryClient): self.__discovery_clients = discovery_clients - def get_instances(self, service_id): + def get_instances(self, service_id: str) -> List[ServiceInstance]: for client in self.__discovery_clients: services = client.get_instances(service_id) if not_none_nor_empty(services): @@ -29,5 +31,5 @@ def get_instances(self, service_id): return [] @property - def services(self): - return flat_map(lambda d: d.services, self.__discovery_clients) + def services(self) -> Set[str]: + return set(flat_map(lambda d: d.services, self.__discovery_clients)) diff --git a/commons/client/discovery/discovery_client.py b/commons/client/discovery/discovery_client.py index dc94e94..d65fa78 100644 --- a/commons/client/discovery/discovery_client.py +++ b/commons/client/discovery/discovery_client.py @@ -8,9 +8,10 @@ # standard library from abc import ABC, abstractmethod +from typing import List, Set # scip plugin -from commons.client.service_instance import StaticServiceInstance +from commons.client.service_instance import ServiceInstance, StaticServiceInstance from commons.utils.functional_operators import filter_get_first __author__ = "Waterball (johnny850807@gmail.com)" @@ -19,7 +20,7 @@ class DiscoveryClient(ABC): @abstractmethod - def get_instances(self, service_id): + def get_instances(self, service_id: str) -> List[ServiceInstance]: """ Gets all ServiceInstances associated with a particular serviceId. :param service_id: (str) The serviceId to query. @@ -29,7 +30,7 @@ def get_instances(self, service_id): @property @abstractmethod - def services(self): + def services(self) -> List[str]: """ :return: All known service IDs (*str). """ @@ -41,18 +42,18 @@ class StaticDiscoveryClient(DiscoveryClient): A DiscoveryClient initialized with the services. """ - def __init__(self, services): - self.__services = services + def __init__(self, instances: List[ServiceInstance]): + self.__instances = instances - def get_instances(self, service_id): - return list(filter(lambda s: s.service_id == service_id, self.services)) + def get_instances(self, service_id: str) -> List[ServiceInstance]: + return list(filter(lambda s: s.service_id == service_id, self.__instances)) @property - def services(self): - return self.__services + def services(self) -> Set[str]: + return {s.service_id for s in self.__instances} -def static_discovery_client(uri, service_id, instance_ids): +def static_discovery_client(uri: str, service_id: str, instance_ids: List[str]) -> StaticDiscoveryClient: """ A helper method that helps create a list of instances from the same service. :param uri the uri of every service's diff --git a/commons/client/service_instance.py b/commons/client/service_instance.py index 1d2f701..a7103e6 100644 --- a/commons/client/service_instance.py +++ b/commons/client/service_instance.py @@ -16,37 +16,37 @@ class ServiceInstance(ABC): @property @abstractmethod - def instance_id(self): + def instance_id(self) -> str: pass @property @abstractmethod - def service_id(self): + def service_id(self) -> str: pass @property @abstractmethod - def host(self): + def host(self) -> str: pass @property @abstractmethod - def port(self): + def port(self) -> int: pass @property @abstractmethod - def secure(self): + def secure(self) -> bool: pass @property @abstractmethod - def uri(self): + def uri(self) -> str: pass @property @abstractmethod - def scheme(self): + def scheme(self) -> str: pass @@ -55,7 +55,7 @@ class StaticServiceInstance(ServiceInstance): A service instance that is initialized with its basic properties """ - def __init__(self, uri, service_id, instance_id): + def __init__(self, uri: str, service_id: str, instance_id: str): """ :param uri: the url in the string type """ @@ -69,29 +69,29 @@ def __init__(self, uri, service_id, instance_id): self._instance_id = instance_id @property - def service_id(self): + def service_id(self) -> str: return self._service_id @property - def instance_id(self): + def instance_id(self) -> str: return self._instance_id @property - def host(self): + def host(self) -> str: return self._host @property - def port(self): + def port(self) -> int: return self._port @property - def secure(self): + def secure(self) -> bool: return self._secure @property - def uri(self): + def uri(self) -> str: return self._uri @property - def scheme(self): + def scheme(self) -> str: return self._scheme diff --git a/commons/utils/functional_operators.py b/commons/utils/functional_operators.py index 386d3da..78ac357 100644 --- a/commons/utils/functional_operators.py +++ b/commons/utils/functional_operators.py @@ -1,25 +1,24 @@ # -*- coding: utf-8 -*- +# standard library +from typing import Iterable __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" -def flat_map(f, xs): +def flat_map(f, xs: Iterable): """ :param f: a mapping function :param xs: (Iterable) :return: list """ - results = [] - for element in xs: - results += f(element) - return results + return [item for element in xs for item in f(element)] -def filter_get_first(f, xs): +def filter_get_first(f, xs: Iterable): """ :param f: a predicate function that returns a boolean - :param xs: (Iterable - :return: an element + :param xs: (Iterable) + :return: the first element matches the predicate """ - return next(filter(f, xs), None) + return [x for x in xs if f(x)][0] diff --git a/commons/utils/list_utils.py b/commons/utils/list_utils.py index 044008d..f9433f3 100644 --- a/commons/utils/list_utils.py +++ b/commons/utils/list_utils.py @@ -4,9 +4,8 @@ __license__ = "Apache 2.0" -def not_none_nor_empty(the_list): +def not_none_nor_empty(the_list: list): """ - :param the_list: a list :return: true if the list is neither none nor empty """ return the_list is not None and len(the_list) != 0 diff --git a/tests/client/discovery/composite_test.py b/tests/client/discovery/composite_test.py index f9eb46d..c1fbaed 100644 --- a/tests/client/discovery/composite_test.py +++ b/tests/client/discovery/composite_test.py @@ -16,17 +16,12 @@ def test_get_instances(): for service_id in range(1, 4): - instances = client.get_instances("service-{}".format(service_id)) + instances = client.get_instances(f"service-{service_id}") for instance_id in range(1, 4): instance = instances[instance_id - 1] - assert "{}-{}".format(service_id, instance_id) == instance.instance_id + assert f"{service_id}-{instance_id}" == instance.instance_id def test_get_services(): - instances = client.services - i = 0 - for service_id in range(1, 4): - for instance_id in range(1, 4): - assert "service-{}".format(service_id) == instances[i].service_id - assert "{}-{}".format(service_id, instance_id) == instances[i].instance_id - i += 1 + services = client.services + assert {"service-1", "service-2", "service-3"} == services From b05986444dd82e2347761c9d4bbb0e7865031fe3 Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 20:41:38 +0800 Subject: [PATCH 09/24] improve filter_get_first utils --- commons/utils/functional_operators.py | 2 +- tests/utils/functional_operators_test.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/commons/utils/functional_operators.py b/commons/utils/functional_operators.py index 78ac357..9c04da7 100644 --- a/commons/utils/functional_operators.py +++ b/commons/utils/functional_operators.py @@ -21,4 +21,4 @@ def filter_get_first(f, xs: Iterable): :param xs: (Iterable) :return: the first element matches the predicate """ - return [x for x in xs if f(x)][0] + return next((x for x in xs if f(x)), None) diff --git a/tests/utils/functional_operators_test.py b/tests/utils/functional_operators_test.py index cf40349..8aa999f 100644 --- a/tests/utils/functional_operators_test.py +++ b/tests/utils/functional_operators_test.py @@ -11,7 +11,13 @@ def test_flat_map(): assert ["a", "b", "c", "1", "2", "3", ""] == results -def test_filter_get_first(): +def test_filter_get_first_Given_numbers(): the_list = [1, 2, 3, 4, 5, 6, 7, 8] result = filter_get_first(lambda e: e > 4, the_list) assert 5 == result + + +def test_filter_get_first_Given_empty_list(): + the_list = [] + result = filter_get_first(lambda e: e > 4, the_list) + assert result is None From 3115215526a941f0558a03f3f3ef522f1672ddaf Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 12:49:39 +0800 Subject: [PATCH 10/24] :sparkles: FixedServiceInstanceListSupplier completed --- .../client/loadbalancer/supplier/__init__.py | 1 + .../service_instance_list_supplier.py | 58 +++++++++++++++++++ commons/client/service_instance.py | 15 ++++- tests/client/loadbalancer/__init__.py | 1 + .../client/loadbalancer/supplier/__init__.py | 1 + .../service_instance_list_supplier_test.py | 19 ++++++ 6 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 commons/client/loadbalancer/supplier/__init__.py create mode 100644 commons/client/loadbalancer/supplier/service_instance_list_supplier.py create mode 100644 tests/client/loadbalancer/__init__.py create mode 100644 tests/client/loadbalancer/supplier/__init__.py create mode 100644 tests/client/loadbalancer/supplier/service_instance_list_supplier_test.py diff --git a/commons/client/loadbalancer/supplier/__init__.py b/commons/client/loadbalancer/supplier/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/commons/client/loadbalancer/supplier/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/commons/client/loadbalancer/supplier/service_instance_list_supplier.py b/commons/client/loadbalancer/supplier/service_instance_list_supplier.py new file mode 100644 index 0000000..203f92d --- /dev/null +++ b/commons/client/loadbalancer/supplier/service_instance_list_supplier.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +""" +Since the load-balancer is responsible for choosing one instance +per service request from a list of instances. We need a ServiceInstanceListSupplier for +each service to decouple the source of the instances from load-balancers. +""" +# standard library +from abc import ABC, abstractmethod + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +class ServiceInstanceListSupplier(ABC): + """ + Non-Reactive version of ServiceInstanceListSupplier. + (Spring Cloud implement the supplier in the reactive way, means that + its supplier returns an Observable which broadcasts the instances on every change.) + + We may consider to adopt reactive programming in the future. + """ + + @property + @abstractmethod + def service_id(self): + """ + :return: (str) the service's id + """ + pass + + @abstractmethod + def get(self, request=None): + """ + :param request (opt) TODO not sure will we need this, + this extension was designed by spring-cloud. + """ + pass + + +class FixedServiceInstanceListSupplier(ServiceInstanceListSupplier): + """ + A supplier that is initialized with fixed instances. (i.e. they won't be changed) + """ + + def __init__(self, service_id, instances): + """ + :param service_id: (str) + :param instances: (*ServiceInstance) + """ + self._service_id = service_id + self._instances = instances + + def get(self, request=None): + return self._instances + + @property + def service_id(self): + return self._service_id diff --git a/commons/client/service_instance.py b/commons/client/service_instance.py index a7103e6..4951070 100644 --- a/commons/client/service_instance.py +++ b/commons/client/service_instance.py @@ -3,7 +3,6 @@ __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" - # standard library from abc import ABC, abstractmethod from urllib.parse import urlparse @@ -95,3 +94,17 @@ def uri(self) -> str: @property def scheme(self) -> str: return self._scheme + + def __eq__(self, o): + if isinstance(o, ServiceInstance): + return ( + self.uri == o.uri + and self.service_id == o.service_id + and self.instance_id == o.instance_id + and self.host == o.host + and self.port == o.port + and self.secure == o.secure + and self.scheme == o.scheme + ) + + return False diff --git a/tests/client/loadbalancer/__init__.py b/tests/client/loadbalancer/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/tests/client/loadbalancer/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/tests/client/loadbalancer/supplier/__init__.py b/tests/client/loadbalancer/supplier/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/tests/client/loadbalancer/supplier/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/tests/client/loadbalancer/supplier/service_instance_list_supplier_test.py b/tests/client/loadbalancer/supplier/service_instance_list_supplier_test.py new file mode 100644 index 0000000..5bb5610 --- /dev/null +++ b/tests/client/loadbalancer/supplier/service_instance_list_supplier_test.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# scip plugin +from commons.client.loadbalancer.supplier.service_instance_list_supplier import FixedServiceInstanceListSupplier +from commons.client.service_instance import StaticServiceInstance + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + +SERVICE_ID = "serviceId" +instances = [StaticServiceInstance("uri", SERVICE_ID, instance_id) for instance_id in ["1", "2", "3"]] +supplier = FixedServiceInstanceListSupplier(SERVICE_ID, instances) + + +def test_get_service_id(): + assert SERVICE_ID == supplier.service_id + + +def test_get_instances(): + assert instances == supplier.get() From 560fe21f89fbc81f83b18dd27c5e0a9a9b76415a Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 16:28:42 +0800 Subject: [PATCH 11/24] :sparkles: CachingServiceInstanceListSupplier completed --- .../client/loadbalancer/supplier/decorator.py | 35 ++++++++++ .../service_instance_list_supplier.py | 25 +++++++ {context => external}/__init__.py | 0 external/cache/__init__.py | 1 + external/cache/cache_manager.py | 68 +++++++++++++++++++ poetry.lock | 20 +++++- pyproject.toml | 1 + .../loadbalancer/supplier/decorator_test.py | 23 +++++++ .../service_instance_list_supplier_test.py | 36 +++++++--- tests/client/loadbalancer/supplier/stubs.py | 10 +++ 10 files changed, 209 insertions(+), 10 deletions(-) create mode 100644 commons/client/loadbalancer/supplier/decorator.py rename {context => external}/__init__.py (100%) create mode 100644 external/cache/__init__.py create mode 100644 external/cache/cache_manager.py create mode 100644 tests/client/loadbalancer/supplier/decorator_test.py create mode 100644 tests/client/loadbalancer/supplier/stubs.py diff --git a/commons/client/loadbalancer/supplier/decorator.py b/commons/client/loadbalancer/supplier/decorator.py new file mode 100644 index 0000000..1f84079 --- /dev/null +++ b/commons/client/loadbalancer/supplier/decorator.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" +# standard library +from abc import ABC + +# scip plugin +from commons.client.loadbalancer.supplier.service_instance_list_supplier import ServiceInstanceListSupplier +from external.cache.cache_manager import CacheManager + + +class DelegatingServiceInstanceListSupplier(ServiceInstanceListSupplier, ABC): + """ + An application of decorator pattern that adds behaviors to ServiceInstanceListSupplier. + The decorators should inherit this class. + """ + + def __init__(self, delegate: ServiceInstanceListSupplier): + assert delegate is not None + self.delegate = delegate + + @property + def service_id(self): + return self.delegate.service_id + + +class CachingServiceInstanceListSupplier(DelegatingServiceInstanceListSupplier): + CACHE_NAME = "CacheKey" + + def __init__(self, cache_manager: CacheManager, delegate: ServiceInstanceListSupplier): + super().__init__(delegate) + self.__cache_manager = cache_manager + + def get(self, request=None): + return self.__cache_manager.get(self.CACHE_NAME).on_cache_miss(self.delegate.get) diff --git a/commons/client/loadbalancer/supplier/service_instance_list_supplier.py b/commons/client/loadbalancer/supplier/service_instance_list_supplier.py index 203f92d..8b9aa12 100644 --- a/commons/client/loadbalancer/supplier/service_instance_list_supplier.py +++ b/commons/client/loadbalancer/supplier/service_instance_list_supplier.py @@ -7,6 +7,9 @@ # standard library from abc import ABC, abstractmethod +# scip plugin +from commons.client.discovery.discovery_client import DiscoveryClient + __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" @@ -33,6 +36,7 @@ def get(self, request=None): """ :param request (opt) TODO not sure will we need this, this extension was designed by spring-cloud. + :return: (*ServiceInstance) a list of instances """ pass @@ -56,3 +60,24 @@ def get(self, request=None): @property def service_id(self): return self._service_id + + +class DiscoveryClientServiceInstanceListSupplier(ServiceInstanceListSupplier): + """ + The adapter delegating to discovery client for querying instances + """ + + def __init__(self, service_id, discovery_client): + """ + :param service_id: (str) + :param discovery_client: (DiscoveryClient) + """ + self.__service_id = service_id + self.__delegate = discovery_client + + @property + def service_id(self): + return self.__service_id + + def get(self, request=None): + return self.__delegate.get_instances() diff --git a/context/__init__.py b/external/__init__.py similarity index 100% rename from context/__init__.py rename to external/__init__.py diff --git a/external/cache/__init__.py b/external/cache/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/external/cache/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/external/cache/cache_manager.py b/external/cache/cache_manager.py new file mode 100644 index 0000000..595826b --- /dev/null +++ b/external/cache/cache_manager.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +""" +A cache manager wrapper that supports some syntax sugar. + +Usage: + value = cache_manager.get(cache_key) \ + .on_cache_miss(lambda: retrieve_value(key)) +""" + + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + +# standard library +from abc import ABC, abstractmethod + + +class OnCacheMiss: + def __init__(self, cache_manager, key, value): + self.__cache_manager = cache_manager + self.__key = key + self.__value = value + + @abstractmethod + def on_cache_miss(self, cache_miss_func): + """ + :param cache_miss_func: (lambda ()->value) + """ + if not self.__value: + value = cache_miss_func() + self.__cache_manager.put(self.__key, value) + return value + return self.__value + + +class CacheManager(ABC): + """ + Service Provider Interface (SPI) for basic caching. + We might want to extend this class with many features in the future. + """ + + def get(self, key) -> OnCacheMiss: + value = self.retrieve_value(key) + return OnCacheMiss(self, key, value) + + @abstractmethod + def retrieve_value(self, key): + pass + + @abstractmethod + def put(self, key, value): + pass + + +class NaiveCacheManager(CacheManager): + """ + A very simple cache implementation that is not concerned about evict-and-replacement. + """ + + def __init__(self): + self.cache_dict = {} + + def retrieve_value(self, key): + return self.cache_dict.get(key, None) + + def put(self, key, value): + self.cache_dict[key] = value diff --git a/poetry.lock b/poetry.lock index 26d957a..d6b5c99 100644 --- a/poetry.lock +++ b/poetry.lock @@ -210,6 +210,20 @@ coverage = ">=4.4" [package.extras] testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] +[[package]] +name = "pytest-mock" +version = "3.3.1" +description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "tox", "pytest-asyncio"] + [[package]] name = "pyyaml" version = "5.3.1" @@ -268,7 +282,7 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "190192c42431e322402cdda9bf4820d92afcffbaca203bb403e552118ff6eb7c" +content-hash = "64c77a7ee3f4c43d2487b8f572e71cdfbadc7d90d31c9b204739cc426db78ba2" [metadata.files] appdirs = [ @@ -379,6 +393,10 @@ pytest-cov = [ {file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"}, {file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"}, ] +pytest-mock = [ + {file = "pytest-mock-3.3.1.tar.gz", hash = "sha256:a4d6d37329e4a893e77d9ffa89e838dd2b45d5dc099984cf03c703ac8411bb82"}, + {file = "pytest_mock-3.3.1-py3-none-any.whl", hash = "sha256:024e405ad382646318c4281948aadf6fe1135632bea9cc67366ea0c4098ef5f2"}, +] pyyaml = [ {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, diff --git a/pyproject.toml b/pyproject.toml index de09a11..beb3e95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ python = "^3.7" pre-commit = "^2.7.1" pytest = "^6.1.2" pytest-cov = "^2.10.1" +pytest-mock = "^3.3.1" [build-system] requires = ["poetry>=0.12"] diff --git a/tests/client/loadbalancer/supplier/decorator_test.py b/tests/client/loadbalancer/supplier/decorator_test.py new file mode 100644 index 0000000..8f03098 --- /dev/null +++ b/tests/client/loadbalancer/supplier/decorator_test.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# standard library +from unittest.mock import Mock + +# scip plugin +from commons.client.loadbalancer.supplier.decorator import CachingServiceInstanceListSupplier +from external.cache.cache_manager import NaiveCacheManager +from tests.client.loadbalancer.supplier.stubs import INSTANCES + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +class TestCachingServiceInstanceListSupplier: + def setup_class(self): + self.delegate = Mock() + self.delegate.get = Mock(return_value=INSTANCES) + self.supplier = CachingServiceInstanceListSupplier(NaiveCacheManager(), self.delegate) + + def test_Given_cache_When_10_invocations_Then_only_1_cache_miss_and_delegate(self): + for i in range(1, 10): + self.supplier.get() + assert self.delegate.get.call_count == 1 diff --git a/tests/client/loadbalancer/supplier/service_instance_list_supplier_test.py b/tests/client/loadbalancer/supplier/service_instance_list_supplier_test.py index 5bb5610..ea8ce93 100644 --- a/tests/client/loadbalancer/supplier/service_instance_list_supplier_test.py +++ b/tests/client/loadbalancer/supplier/service_instance_list_supplier_test.py @@ -1,19 +1,37 @@ # -*- coding: utf-8 -*- +# standard library +from unittest.mock import Mock + # scip plugin -from commons.client.loadbalancer.supplier.service_instance_list_supplier import FixedServiceInstanceListSupplier -from commons.client.service_instance import StaticServiceInstance +from commons.client.loadbalancer.supplier.service_instance_list_supplier import ( + DiscoveryClientServiceInstanceListSupplier, + FixedServiceInstanceListSupplier, +) +from tests.client.loadbalancer.supplier.stubs import INSTANCES, SERVICE_ID __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" -SERVICE_ID = "serviceId" -instances = [StaticServiceInstance("uri", SERVICE_ID, instance_id) for instance_id in ["1", "2", "3"]] -supplier = FixedServiceInstanceListSupplier(SERVICE_ID, instances) + +class TestFixedServiceInstanceListSupplier: + def setup_class(self): + self.supplier = FixedServiceInstanceListSupplier(SERVICE_ID, INSTANCES) + + def test_get_service_id(self): + assert SERVICE_ID == self.supplier.service_id + + def test_get_instances(self): + assert INSTANCES == self.supplier.get() -def test_get_service_id(): - assert SERVICE_ID == supplier.service_id +class TestDiscoveryClientServiceInstanceListSupplier: + def setup_class(self): + self.discovery_client = Mock() + self.discovery_client.get_instances = Mock(return_value=INSTANCES) + self.supplier = DiscoveryClientServiceInstanceListSupplier(SERVICE_ID, self.discovery_client) + def test_get_service_id(self): + assert SERVICE_ID == self.supplier.service_id -def test_get_instances(): - assert instances == supplier.get() + def test_get_instances(self): + assert INSTANCES == self.supplier.get() diff --git a/tests/client/loadbalancer/supplier/stubs.py b/tests/client/loadbalancer/supplier/stubs.py new file mode 100644 index 0000000..3269dab --- /dev/null +++ b/tests/client/loadbalancer/supplier/stubs.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# scip plugin +from commons.client.service_instance import StaticServiceInstance + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +SERVICE_ID = "serviceId" +INSTANCES = [StaticServiceInstance("uri", SERVICE_ID, instance_id) for instance_id in ["1", "2", "3"]] From 72d9118577d41acd60d6c774088f0372ffd5107b Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 20:24:25 +0800 Subject: [PATCH 12/24] :art: Add type checking syntax --- .../client/loadbalancer/supplier/decorator.py | 10 ++++++++-- .../service_instance_list_supplier.py | 20 ++++++++++--------- external/cache/cache_manager.py | 3 ++- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/commons/client/loadbalancer/supplier/decorator.py b/commons/client/loadbalancer/supplier/decorator.py index 1f84079..c6873da 100644 --- a/commons/client/loadbalancer/supplier/decorator.py +++ b/commons/client/loadbalancer/supplier/decorator.py @@ -1,4 +1,10 @@ # -*- coding: utf-8 -*- +# standard library +from typing import List + +# scip plugin +from commons.client.service_instance import ServiceInstance + __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" # standard library @@ -20,7 +26,7 @@ def __init__(self, delegate: ServiceInstanceListSupplier): self.delegate = delegate @property - def service_id(self): + def service_id(self) -> str: return self.delegate.service_id @@ -31,5 +37,5 @@ def __init__(self, cache_manager: CacheManager, delegate: ServiceInstanceListSup super().__init__(delegate) self.__cache_manager = cache_manager - def get(self, request=None): + def get(self, request=None) -> List[ServiceInstance]: return self.__cache_manager.get(self.CACHE_NAME).on_cache_miss(self.delegate.get) diff --git a/commons/client/loadbalancer/supplier/service_instance_list_supplier.py b/commons/client/loadbalancer/supplier/service_instance_list_supplier.py index 8b9aa12..6c046e7 100644 --- a/commons/client/loadbalancer/supplier/service_instance_list_supplier.py +++ b/commons/client/loadbalancer/supplier/service_instance_list_supplier.py @@ -6,9 +6,11 @@ """ # standard library from abc import ABC, abstractmethod +from typing import List # scip plugin from commons.client.discovery.discovery_client import DiscoveryClient +from commons.client.service_instance import ServiceInstance __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" @@ -25,14 +27,14 @@ class ServiceInstanceListSupplier(ABC): @property @abstractmethod - def service_id(self): + def service_id(self) -> str: """ :return: (str) the service's id """ pass @abstractmethod - def get(self, request=None): + def get(self, request=None) -> List[ServiceInstance]: """ :param request (opt) TODO not sure will we need this, this extension was designed by spring-cloud. @@ -46,7 +48,7 @@ class FixedServiceInstanceListSupplier(ServiceInstanceListSupplier): A supplier that is initialized with fixed instances. (i.e. they won't be changed) """ - def __init__(self, service_id, instances): + def __init__(self, service_id: str, instances: List[ServiceInstance]): """ :param service_id: (str) :param instances: (*ServiceInstance) @@ -54,11 +56,11 @@ def __init__(self, service_id, instances): self._service_id = service_id self._instances = instances - def get(self, request=None): + def get(self, request=None) -> List[ServiceInstance]: return self._instances @property - def service_id(self): + def service_id(self) -> str: return self._service_id @@ -67,7 +69,7 @@ class DiscoveryClientServiceInstanceListSupplier(ServiceInstanceListSupplier): The adapter delegating to discovery client for querying instances """ - def __init__(self, service_id, discovery_client): + def __init__(self, service_id: str, discovery_client: DiscoveryClient): """ :param service_id: (str) :param discovery_client: (DiscoveryClient) @@ -76,8 +78,8 @@ def __init__(self, service_id, discovery_client): self.__delegate = discovery_client @property - def service_id(self): + def service_id(self) -> str: return self.__service_id - def get(self, request=None): - return self.__delegate.get_instances() + def get(self, request=None) -> List[ServiceInstance]: + return self.__delegate.get_instances(self.service_id) diff --git a/external/cache/cache_manager.py b/external/cache/cache_manager.py index 595826b..65a5862 100644 --- a/external/cache/cache_manager.py +++ b/external/cache/cache_manager.py @@ -38,6 +38,7 @@ class CacheManager(ABC): """ Service Provider Interface (SPI) for basic caching. We might want to extend this class with many features in the future. + (e.g. timeout, evict-and-replacement) """ def get(self, key) -> OnCacheMiss: @@ -55,7 +56,7 @@ def put(self, key, value): class NaiveCacheManager(CacheManager): """ - A very simple cache implementation that is not concerned about evict-and-replacement. + A very simple cache implementation. """ def __init__(self): From 464b9dd832247c2b11528a1658f93aac647e798d Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 21:13:41 +0800 Subject: [PATCH 13/24] :art: Add NoneTypeError and some type checking --- .../client/loadbalancer/supplier/decorator.py | 3 ++- commons/client/service_instance.py | 2 +- commons/exceptions/__init__.py | 7 +++++++ commons/exceptions/primitive.py | 17 +++++++++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 commons/exceptions/__init__.py create mode 100644 commons/exceptions/primitive.py diff --git a/commons/client/loadbalancer/supplier/decorator.py b/commons/client/loadbalancer/supplier/decorator.py index c6873da..6fe9566 100644 --- a/commons/client/loadbalancer/supplier/decorator.py +++ b/commons/client/loadbalancer/supplier/decorator.py @@ -4,6 +4,7 @@ # scip plugin from commons.client.service_instance import ServiceInstance +from commons.exceptions.primitive import NoneTypeError __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" @@ -22,7 +23,7 @@ class DelegatingServiceInstanceListSupplier(ServiceInstanceListSupplier, ABC): """ def __init__(self, delegate: ServiceInstanceListSupplier): - assert delegate is not None + NoneTypeError.raise_if_none(delegate) self.delegate = delegate @property diff --git a/commons/client/service_instance.py b/commons/client/service_instance.py index 4951070..ad7acfa 100644 --- a/commons/client/service_instance.py +++ b/commons/client/service_instance.py @@ -95,7 +95,7 @@ def uri(self) -> str: def scheme(self) -> str: return self._scheme - def __eq__(self, o): + def __eq__(self, o: object) -> bool: if isinstance(o, ServiceInstance): return ( self.uri == o.uri diff --git a/commons/exceptions/__init__.py b/commons/exceptions/__init__.py new file mode 100644 index 0000000..e59d4cf --- /dev/null +++ b/commons/exceptions/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +""" + +""" + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" diff --git a/commons/exceptions/primitive.py b/commons/exceptions/primitive.py new file mode 100644 index 0000000..f2efcd5 --- /dev/null +++ b/commons/exceptions/primitive.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +""" +All primitive exceptions are included here +""" + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +class NoneTypeError(Exception): + @staticmethod + def raise_if_none(obj): + if obj is None: + raise NoneTypeError + + def __init__(self, message): + self.message = message From 0b5401773f6fbabd248b6f1eecf44d6b4a8b9a8c Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 00:33:22 +0800 Subject: [PATCH 14/24] :beer: implement DiscoveryClient design --- commons/__init__.py | 0 commons/client/ServiceInstance.py | 90 ++++++++++++++++++++ commons/client/__init__.py | 0 commons/client/discovery/__init__.py | 0 commons/client/discovery/composite.py | 23 +++++ commons/client/discovery/discovery_client.py | 49 +++++++++++ commons/client/loadbalancer/__init__.py | 0 commons/client/service_registry/__init__.py | 0 commons/utils/__init__.py | 0 commons/utils/functional_operators.py | 13 +++ commons/utils/list_utils.py | 6 ++ context/__init__.py | 0 pyproject.toml | 2 + src/__init__.py | 13 --- src/stub.py | 17 ---- tests/__init__.py | 13 --- tests/client/__init__.py | 0 tests/client/discovery/__init__.py | 0 tests/client/discovery/composite_test.py | 30 +++++++ tests/stub_test.py | 20 ----- 20 files changed, 213 insertions(+), 63 deletions(-) create mode 100644 commons/__init__.py create mode 100644 commons/client/ServiceInstance.py create mode 100644 commons/client/__init__.py create mode 100644 commons/client/discovery/__init__.py create mode 100644 commons/client/discovery/composite.py create mode 100644 commons/client/discovery/discovery_client.py create mode 100644 commons/client/loadbalancer/__init__.py create mode 100644 commons/client/service_registry/__init__.py create mode 100644 commons/utils/__init__.py create mode 100644 commons/utils/functional_operators.py create mode 100644 commons/utils/list_utils.py create mode 100644 context/__init__.py delete mode 100644 src/__init__.py delete mode 100644 src/stub.py create mode 100644 tests/client/__init__.py create mode 100644 tests/client/discovery/__init__.py create mode 100644 tests/client/discovery/composite_test.py delete mode 100644 tests/stub_test.py diff --git a/commons/__init__.py b/commons/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/commons/client/ServiceInstance.py b/commons/client/ServiceInstance.py new file mode 100644 index 0000000..c716605 --- /dev/null +++ b/commons/client/ServiceInstance.py @@ -0,0 +1,90 @@ +# standard library +from abc import ABC, abstractmethod +from urllib.parse import urlparse + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +class ServiceInstance(ABC): + @property + @abstractmethod + def instance_id(self): + pass + + @property + @abstractmethod + def service_id(self): + pass + + @property + @abstractmethod + def host(self): + pass + + @property + @abstractmethod + def port(self): + pass + + @property + @abstractmethod + def secure(self): + pass + + @property + @abstractmethod + def uri(self): + pass + + @property + @abstractmethod + def scheme(self): + pass + + +class StaticServiceInstance(ServiceInstance): + """ + A service instance that is initialized with its basic properties + """ + + def __init__(self, uri, service_id, instance_id): + """ + :param uri: the url in the string type + """ + url_obj = urlparse(uri) + self._uri = uri + self._scheme = url_obj.scheme + self._secure = self._scheme == "https" + self._host = url_obj.netloc + self._port = url_obj.port + self._service_id = service_id + self._instance_id = instance_id + + @property + def service_id(self): + return self._service_id + + @property + def instance_id(self): + return self._instance_id + + @property + def host(self): + return self._host + + @property + def port(self): + return self._port + + @property + def secure(self): + return self._secure + + @property + def uri(self): + return self._uri + + @property + def scheme(self): + return self._scheme diff --git a/commons/client/__init__.py b/commons/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/commons/client/discovery/__init__.py b/commons/client/discovery/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/commons/client/discovery/composite.py b/commons/client/discovery/composite.py new file mode 100644 index 0000000..cb39845 --- /dev/null +++ b/commons/client/discovery/composite.py @@ -0,0 +1,23 @@ +# scip plugin +from commons.client.discovery.discovery_client import DiscoveryClient +from commons.utils.functional_operators import flat_map +from commons.utils.list_utils import not_null_nor_empty + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +class CompositeDiscoveryClient(DiscoveryClient): + def __init__(self, *discovery_clients): + self.discovery_clients = discovery_clients + + def get_instances(self, service_id): + for client in self.discovery_clients: + services = client.get_instances(service_id) + if not_null_nor_empty(services): + return services + return [] + + @property + def services(self): + return flat_map(lambda d: d.services, self.discovery_clients) diff --git a/commons/client/discovery/discovery_client.py b/commons/client/discovery/discovery_client.py new file mode 100644 index 0000000..a3bca2e --- /dev/null +++ b/commons/client/discovery/discovery_client.py @@ -0,0 +1,49 @@ +# standard library +from abc import ABC, abstractmethod + +# scip plugin +from commons.client.ServiceInstance import StaticServiceInstance +from commons.utils.functional_operators import filter_get_first + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +class DiscoveryClient(ABC): + @abstractmethod + def get_instances(self, service_id): + """ + Gets all ServiceInstances associated with a particular serviceId. + :param service_id: The serviceId to query. + :return: A List of ServiceInstance. + """ + pass + + @property + @abstractmethod + def services(self): + """ + :return: All known service IDs. + """ + pass + + +class StaticDiscoveryClient(DiscoveryClient): + """ + A DiscoveryClient initialized with the services + """ + + def __init__(self, services): + self._services = services + + def get_instances(self, service_id): + return list(filter(lambda s: s.service_id == service_id, self.services)) + + @property + def services(self): + return self._services + + +def static_discovery_client(uri, service_id, instance_ids): + services = [StaticServiceInstance(uri, service_id, instance_id) for instance_id in instance_ids] + return StaticDiscoveryClient(services) diff --git a/commons/client/loadbalancer/__init__.py b/commons/client/loadbalancer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/commons/client/service_registry/__init__.py b/commons/client/service_registry/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/commons/utils/__init__.py b/commons/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/commons/utils/functional_operators.py b/commons/utils/functional_operators.py new file mode 100644 index 0000000..013b851 --- /dev/null +++ b/commons/utils/functional_operators.py @@ -0,0 +1,13 @@ +# standard library +from functools import reduce + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +def flat_map(f, xs): + return reduce(lambda a, b: a + b, map(f, xs)) + + +def filter_get_first(f, the_list): + return next(filter(f, the_list), None) diff --git a/commons/utils/list_utils.py b/commons/utils/list_utils.py new file mode 100644 index 0000000..6d403ff --- /dev/null +++ b/commons/utils/list_utils.py @@ -0,0 +1,6 @@ +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +def not_null_nor_empty(the_list): + return the_list is not None and len(the_list) != 0 diff --git a/context/__init__.py b/context/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml index de09a11..f3222f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,8 @@ license = "Apache-2.0" [tool.poetry.dependencies] python = "^3.7" +pytest = "^6.1.2" +pytest-cov = "^2.10.1" [tool.poetry.dev-dependencies] pre-commit = "^2.7.1" diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index b9b3c0c..0000000 --- a/src/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2020 Waterball (johnny850807@gmail.com) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/src/stub.py b/src/stub.py deleted file mode 100644 index 56d99ca..0000000 --- a/src/stub.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2020 Waterball (johnny850807@gmail.com) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -def add(a, b): - return a + b diff --git a/tests/__init__.py b/tests/__init__.py index b9b3c0c..e69de29 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,13 +0,0 @@ -# Copyright 2020 Waterball (johnny850807@gmail.com) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tests/client/__init__.py b/tests/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/client/discovery/__init__.py b/tests/client/discovery/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/client/discovery/composite_test.py b/tests/client/discovery/composite_test.py new file mode 100644 index 0000000..39a4990 --- /dev/null +++ b/tests/client/discovery/composite_test.py @@ -0,0 +1,30 @@ +# scip plugin +from commons.client.discovery.composite import CompositeDiscoveryClient +from commons.client.discovery.discovery_client import static_discovery_client + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache" + +client = CompositeDiscoveryClient( + static_discovery_client("url-1", "service-1", ["1-1", "1-2", "1-3"]), + static_discovery_client("url-2", "service-2", ["2-1", "2-2", "2-3"]), + static_discovery_client("url-3", "service-3", ["3-1", "3-2", "3-3"]), +) + + +def test_get_instances(): + for service_id in range(1, 4): + instances = client.get_instances("service-{}".format(service_id)) + for instance_id in range(1, 4): + instance = instances[instance_id - 1] + assert instance.instance_id == "{}-{}".format(service_id, instance_id) + + +def test_get_services(): + instances = client.services + i = 0 + for service_id in range(1, 4): + for instance_id in range(1, 4): + assert instances[i].service_id == "service-{}".format(service_id) + assert instances[i].instance_id == "{}-{}".format(service_id, instance_id) + i += 1 diff --git a/tests/stub_test.py b/tests/stub_test.py deleted file mode 100644 index de90136..0000000 --- a/tests/stub_test.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2020 Waterball (johnny850807@gmail.com) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# scip plugin -from src import stub - - -def test(): - assert 10 == stub.add(4, 6) From 83a45fe1c6d16fb63a7bc1716aa6e7a9a3d33638 Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 01:06:38 +0800 Subject: [PATCH 15/24] Add more comments --- commons/client/discovery/composite.py | 4 ++++ commons/client/discovery/discovery_client.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/commons/client/discovery/composite.py b/commons/client/discovery/composite.py index cb39845..ddc3432 100644 --- a/commons/client/discovery/composite.py +++ b/commons/client/discovery/composite.py @@ -8,6 +8,10 @@ class CompositeDiscoveryClient(DiscoveryClient): + """ + Composite pattern application: aggregate the service sources from a list of discovery client + """ + def __init__(self, *discovery_clients): self.discovery_clients = discovery_clients diff --git a/commons/client/discovery/discovery_client.py b/commons/client/discovery/discovery_client.py index a3bca2e..c5fb607 100644 --- a/commons/client/discovery/discovery_client.py +++ b/commons/client/discovery/discovery_client.py @@ -45,5 +45,9 @@ def services(self): def static_discovery_client(uri, service_id, instance_ids): + """ + Usage: + static_discovery_client("url-1", "service-1", ["id-1", "id-2", "id-3"]) + """ services = [StaticServiceInstance(uri, service_id, instance_id) for instance_id in instance_ids] return StaticDiscoveryClient(services) From d65d60d10a3c2012814934d9b7553f2cb3261614 Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 11:32:19 +0800 Subject: [PATCH 16/24] Modify some functional operation concerning performance --- README.md | 6 ++++++ commons/client/ServiceInstance.py | 4 ++++ commons/client/discovery/composite.py | 4 ++-- commons/utils/functional_operators.py | 7 +++++-- commons/utils/list_utils.py | 7 ++++++- pyproject.toml | 2 -- tests/client/discovery/composite_test.py | 6 +++--- tests/utils/__init__.py | 2 ++ tests/utils/functional_operators_test.py | 17 +++++++++++++++++ 9 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 tests/utils/__init__.py create mode 100644 tests/utils/functional_operators_test.py diff --git a/README.md b/README.md index 36e8225..bf1e281 100644 --- a/README.md +++ b/README.md @@ -49,3 +49,9 @@ poetry run pre-commit install ``` 6. Start to commit :) + +## Test + +1. Show test coverage report on terminal + +`poetry run pytest --cov-report term --cov ${package} tests/` diff --git a/commons/client/ServiceInstance.py b/commons/client/ServiceInstance.py index c716605..0edc3ba 100644 --- a/commons/client/ServiceInstance.py +++ b/commons/client/ServiceInstance.py @@ -7,6 +7,10 @@ class ServiceInstance(ABC): + """ + + """ + @property @abstractmethod def instance_id(self): diff --git a/commons/client/discovery/composite.py b/commons/client/discovery/composite.py index ddc3432..c3be2fa 100644 --- a/commons/client/discovery/composite.py +++ b/commons/client/discovery/composite.py @@ -1,7 +1,7 @@ # scip plugin from commons.client.discovery.discovery_client import DiscoveryClient from commons.utils.functional_operators import flat_map -from commons.utils.list_utils import not_null_nor_empty +from commons.utils.list_utils import not_none_nor_empty __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" @@ -18,7 +18,7 @@ def __init__(self, *discovery_clients): def get_instances(self, service_id): for client in self.discovery_clients: services = client.get_instances(service_id) - if not_null_nor_empty(services): + if not_none_nor_empty(services): return services return [] diff --git a/commons/utils/functional_operators.py b/commons/utils/functional_operators.py index 013b851..54463f6 100644 --- a/commons/utils/functional_operators.py +++ b/commons/utils/functional_operators.py @@ -6,8 +6,11 @@ def flat_map(f, xs): - return reduce(lambda a, b: a + b, map(f, xs)) + results = [] + for element in xs: + results += f(element) + return results def filter_get_first(f, the_list): - return next(filter(f, the_list), None) + return [x for x in the_list if f(x)][0] diff --git a/commons/utils/list_utils.py b/commons/utils/list_utils.py index 6d403ff..8e2c8e3 100644 --- a/commons/utils/list_utils.py +++ b/commons/utils/list_utils.py @@ -2,5 +2,10 @@ __license__ = "Apache 2.0" -def not_null_nor_empty(the_list): +def not_none_nor_empty(the_list): + """ + :param the_list: a list + :return: true if the list is neither none nor empty + """ + assert not the_list or isinstance(the_list, list) return the_list is not None and len(the_list) != 0 diff --git a/pyproject.toml b/pyproject.toml index f3222f3..de09a11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,8 +7,6 @@ license = "Apache-2.0" [tool.poetry.dependencies] python = "^3.7" -pytest = "^6.1.2" -pytest-cov = "^2.10.1" [tool.poetry.dev-dependencies] pre-commit = "^2.7.1" diff --git a/tests/client/discovery/composite_test.py b/tests/client/discovery/composite_test.py index 39a4990..758c4e3 100644 --- a/tests/client/discovery/composite_test.py +++ b/tests/client/discovery/composite_test.py @@ -17,7 +17,7 @@ def test_get_instances(): instances = client.get_instances("service-{}".format(service_id)) for instance_id in range(1, 4): instance = instances[instance_id - 1] - assert instance.instance_id == "{}-{}".format(service_id, instance_id) + assert "{}-{}".format(service_id, instance_id) == instance.instance_id def test_get_services(): @@ -25,6 +25,6 @@ def test_get_services(): i = 0 for service_id in range(1, 4): for instance_id in range(1, 4): - assert instances[i].service_id == "service-{}".format(service_id) - assert instances[i].instance_id == "{}-{}".format(service_id, instance_id) + assert "service-{}".format(service_id) == instances[i].service_id + assert "{}-{}".format(service_id, instance_id) == instances[i].instance_id i += 1 diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py new file mode 100644 index 0000000..d7b1237 --- /dev/null +++ b/tests/utils/__init__.py @@ -0,0 +1,2 @@ +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" diff --git a/tests/utils/functional_operators_test.py b/tests/utils/functional_operators_test.py new file mode 100644 index 0000000..cf40349 --- /dev/null +++ b/tests/utils/functional_operators_test.py @@ -0,0 +1,17 @@ +# scip plugin +from commons.utils.functional_operators import filter_get_first, flat_map + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +def test_flat_map(): + the_list = ["a,b,c", "1,2,3", ""] + results = flat_map(lambda e: e.split(","), the_list) + assert ["a", "b", "c", "1", "2", "3", ""] == results + + +def test_filter_get_first(): + the_list = [1, 2, 3, 4, 5, 6, 7, 8] + result = filter_get_first(lambda e: e > 4, the_list) + assert 5 == result From 37e1afee3e007ec33aa9f8582f27df51557f6de4 Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 11:54:22 +0800 Subject: [PATCH 17/24] :memo: add typing and docstrings --- commons/__init__.py | 1 + commons/client/ServiceInstance.py | 11 +++++++---- commons/client/__init__.py | 1 + commons/client/discovery/__init__.py | 1 + commons/client/discovery/composite.py | 14 ++++++++++---- commons/client/discovery/discovery_client.py | 18 +++++++++++++++--- commons/client/loadbalancer/__init__.py | 1 + commons/client/service_registry/__init__.py | 1 + commons/utils/__init__.py | 1 + commons/utils/functional_operators.py | 17 +++++++++++++---- commons/utils/list_utils.py | 2 ++ context/__init__.py | 1 + tests/__init__.py | 1 + tests/client/__init__.py | 1 + tests/client/discovery/__init__.py | 1 + tests/client/discovery/composite_test.py | 8 +++++--- 16 files changed, 62 insertions(+), 18 deletions(-) diff --git a/commons/__init__.py b/commons/__init__.py index e69de29..40a96af 100644 --- a/commons/__init__.py +++ b/commons/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/commons/client/ServiceInstance.py b/commons/client/ServiceInstance.py index 0edc3ba..1d2f701 100644 --- a/commons/client/ServiceInstance.py +++ b/commons/client/ServiceInstance.py @@ -1,14 +1,17 @@ -# standard library -from abc import ABC, abstractmethod -from urllib.parse import urlparse +# -*- coding: utf-8 -*- __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" +# standard library +from abc import ABC, abstractmethod +from urllib.parse import urlparse + + class ServiceInstance(ABC): """ - + A service's instance that owns basic HTTP info. """ @property diff --git a/commons/client/__init__.py b/commons/client/__init__.py index e69de29..40a96af 100644 --- a/commons/client/__init__.py +++ b/commons/client/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/commons/client/discovery/__init__.py b/commons/client/discovery/__init__.py index e69de29..40a96af 100644 --- a/commons/client/discovery/__init__.py +++ b/commons/client/discovery/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/commons/client/discovery/composite.py b/commons/client/discovery/composite.py index c3be2fa..7bc38b0 100644 --- a/commons/client/discovery/composite.py +++ b/commons/client/discovery/composite.py @@ -1,18 +1,24 @@ +# -*- coding: utf-8 -*- + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + # scip plugin from commons.client.discovery.discovery_client import DiscoveryClient from commons.utils.functional_operators import flat_map from commons.utils.list_utils import not_none_nor_empty -__author__ = "Waterball (johnny850807@gmail.com)" -__license__ = "Apache 2.0" - class CompositeDiscoveryClient(DiscoveryClient): """ - Composite pattern application: aggregate the service sources from a list of discovery client + Composite pattern application: + aggregate the service sources from a list of discovery client """ def __init__(self, *discovery_clients): + """ + :param discovery_clients: a list of DiscoveryClient + """ self.discovery_clients = discovery_clients def get_instances(self, service_id): diff --git a/commons/client/discovery/discovery_client.py b/commons/client/discovery/discovery_client.py index c5fb607..6606b9e 100644 --- a/commons/client/discovery/discovery_client.py +++ b/commons/client/discovery/discovery_client.py @@ -1,3 +1,11 @@ +# -*- coding: utf-8 -*- +""" +DiscoveryClient is responsible for providing a list of service's instances. +There may be many services, for example: 'user-service', 'order-service', 'product-service', and so on. +Where each service may have several instances registered (e.g. user-service-1, user-service-2, ...), +and the registered instances are called ServiceInstances. +""" + # standard library from abc import ABC, abstractmethod @@ -14,7 +22,7 @@ class DiscoveryClient(ABC): def get_instances(self, service_id): """ Gets all ServiceInstances associated with a particular serviceId. - :param service_id: The serviceId to query. + :param service_id: (str) The serviceId to query. :return: A List of ServiceInstance. """ pass @@ -23,14 +31,14 @@ def get_instances(self, service_id): @abstractmethod def services(self): """ - :return: All known service IDs. + :return: All known service IDs (*str). """ pass class StaticDiscoveryClient(DiscoveryClient): """ - A DiscoveryClient initialized with the services + A DiscoveryClient initialized with the services. """ def __init__(self, services): @@ -46,6 +54,10 @@ def services(self): def static_discovery_client(uri, service_id, instance_ids): """ + A helper method that helps create a list of instances from the same service. + :param uri the uri of every service's + :param service_id the service_id of the service + :param instance_ids a list of ids (*str) of the instances' Usage: static_discovery_client("url-1", "service-1", ["id-1", "id-2", "id-3"]) """ diff --git a/commons/client/loadbalancer/__init__.py b/commons/client/loadbalancer/__init__.py index e69de29..40a96af 100644 --- a/commons/client/loadbalancer/__init__.py +++ b/commons/client/loadbalancer/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/commons/client/service_registry/__init__.py b/commons/client/service_registry/__init__.py index e69de29..40a96af 100644 --- a/commons/client/service_registry/__init__.py +++ b/commons/client/service_registry/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/commons/utils/__init__.py b/commons/utils/__init__.py index e69de29..40a96af 100644 --- a/commons/utils/__init__.py +++ b/commons/utils/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/commons/utils/functional_operators.py b/commons/utils/functional_operators.py index 54463f6..084fb45 100644 --- a/commons/utils/functional_operators.py +++ b/commons/utils/functional_operators.py @@ -1,16 +1,25 @@ -# standard library -from functools import reduce +# -*- coding: utf-8 -*- __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" def flat_map(f, xs): + """ + :param f: a mapping function + :param xs: (Iterable) + :return: list + """ results = [] for element in xs: results += f(element) return results -def filter_get_first(f, the_list): - return [x for x in the_list if f(x)][0] +def filter_get_first(f, xs): + """ + :param f: a mapping function + :param xs: (Iterable) + :return: an element + """ + return [x for x in xs if f(x)][0] diff --git a/commons/utils/list_utils.py b/commons/utils/list_utils.py index 8e2c8e3..5185483 100644 --- a/commons/utils/list_utils.py +++ b/commons/utils/list_utils.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" diff --git a/context/__init__.py b/context/__init__.py index e69de29..40a96af 100644 --- a/context/__init__.py +++ b/context/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..40a96af 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/tests/client/__init__.py b/tests/client/__init__.py index e69de29..40a96af 100644 --- a/tests/client/__init__.py +++ b/tests/client/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/tests/client/discovery/__init__.py b/tests/client/discovery/__init__.py index e69de29..40a96af 100644 --- a/tests/client/discovery/__init__.py +++ b/tests/client/discovery/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/tests/client/discovery/composite_test.py b/tests/client/discovery/composite_test.py index 758c4e3..f9eb46d 100644 --- a/tests/client/discovery/composite_test.py +++ b/tests/client/discovery/composite_test.py @@ -1,10 +1,12 @@ +# -*- coding: utf-8 -*- + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + # scip plugin from commons.client.discovery.composite import CompositeDiscoveryClient from commons.client.discovery.discovery_client import static_discovery_client -__author__ = "Waterball (johnny850807@gmail.com)" -__license__ = "Apache" - client = CompositeDiscoveryClient( static_discovery_client("url-1", "service-1", ["1-1", "1-2", "1-3"]), static_discovery_client("url-2", "service-2", ["2-1", "2-2", "2-3"]), From b2bb27b9105334af946645434d88c49557b9d7bb Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 12:31:09 +0800 Subject: [PATCH 18/24] Make some fields private --- commons/client/discovery/composite.py | 6 +++--- commons/client/discovery/discovery_client.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/commons/client/discovery/composite.py b/commons/client/discovery/composite.py index 7bc38b0..bd4ecb2 100644 --- a/commons/client/discovery/composite.py +++ b/commons/client/discovery/composite.py @@ -19,10 +19,10 @@ def __init__(self, *discovery_clients): """ :param discovery_clients: a list of DiscoveryClient """ - self.discovery_clients = discovery_clients + self.__discovery_clients = discovery_clients def get_instances(self, service_id): - for client in self.discovery_clients: + for client in self.__discovery_clients: services = client.get_instances(service_id) if not_none_nor_empty(services): return services @@ -30,4 +30,4 @@ def get_instances(self, service_id): @property def services(self): - return flat_map(lambda d: d.services, self.discovery_clients) + return flat_map(lambda d: d.services, self.__discovery_clients) diff --git a/commons/client/discovery/discovery_client.py b/commons/client/discovery/discovery_client.py index 6606b9e..f013fc7 100644 --- a/commons/client/discovery/discovery_client.py +++ b/commons/client/discovery/discovery_client.py @@ -42,14 +42,14 @@ class StaticDiscoveryClient(DiscoveryClient): """ def __init__(self, services): - self._services = services + self.__services = services def get_instances(self, service_id): return list(filter(lambda s: s.service_id == service_id, self.services)) @property def services(self): - return self._services + return self.__services def static_discovery_client(uri, service_id, instance_ids): From 3c1a01457b7a9cb5170cacc5e3e1611c1544d8d6 Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 12:32:27 +0800 Subject: [PATCH 19/24] Fix naming --- commons/client/discovery/discovery_client.py | 2 +- commons/client/{ServiceInstance.py => service_instance.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename commons/client/{ServiceInstance.py => service_instance.py} (100%) diff --git a/commons/client/discovery/discovery_client.py b/commons/client/discovery/discovery_client.py index f013fc7..dc94e94 100644 --- a/commons/client/discovery/discovery_client.py +++ b/commons/client/discovery/discovery_client.py @@ -10,7 +10,7 @@ from abc import ABC, abstractmethod # scip plugin -from commons.client.ServiceInstance import StaticServiceInstance +from commons.client.service_instance import StaticServiceInstance from commons.utils.functional_operators import filter_get_first __author__ = "Waterball (johnny850807@gmail.com)" diff --git a/commons/client/ServiceInstance.py b/commons/client/service_instance.py similarity index 100% rename from commons/client/ServiceInstance.py rename to commons/client/service_instance.py From fbc9a100de4ed049f204e306fbdc7c29da09e34b Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 14:57:32 +0800 Subject: [PATCH 20/24] pending discussion: next+filter vs list comprehension --- commons/utils/functional_operators.py | 6 +++--- commons/utils/list_utils.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/commons/utils/functional_operators.py b/commons/utils/functional_operators.py index 084fb45..386d3da 100644 --- a/commons/utils/functional_operators.py +++ b/commons/utils/functional_operators.py @@ -18,8 +18,8 @@ def flat_map(f, xs): def filter_get_first(f, xs): """ - :param f: a mapping function - :param xs: (Iterable) + :param f: a predicate function that returns a boolean + :param xs: (Iterable :return: an element """ - return [x for x in xs if f(x)][0] + return next(filter(f, xs), None) diff --git a/commons/utils/list_utils.py b/commons/utils/list_utils.py index 5185483..044008d 100644 --- a/commons/utils/list_utils.py +++ b/commons/utils/list_utils.py @@ -9,5 +9,4 @@ def not_none_nor_empty(the_list): :param the_list: a list :return: true if the list is neither none nor empty """ - assert not the_list or isinstance(the_list, list) return the_list is not None and len(the_list) != 0 From 5510384774a9d44ce9025cbd7a248816f7802779 Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 20:22:58 +0800 Subject: [PATCH 21/24] :art: Add type checking syntax --- commons/client/discovery/composite.py | 16 ++++++----- commons/client/discovery/discovery_client.py | 21 +++++++------- commons/client/service_instance.py | 30 ++++++++++---------- commons/utils/functional_operators.py | 17 ++++++----- commons/utils/list_utils.py | 3 +- tests/client/discovery/composite_test.py | 13 +++------ 6 files changed, 48 insertions(+), 52 deletions(-) diff --git a/commons/client/discovery/composite.py b/commons/client/discovery/composite.py index bd4ecb2..5541ec0 100644 --- a/commons/client/discovery/composite.py +++ b/commons/client/discovery/composite.py @@ -1,4 +1,9 @@ # -*- coding: utf-8 -*- +# standard library +from typing import List, Set + +# scip plugin +from commons.client.service_instance import ServiceInstance __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" @@ -15,13 +20,10 @@ class CompositeDiscoveryClient(DiscoveryClient): aggregate the service sources from a list of discovery client """ - def __init__(self, *discovery_clients): - """ - :param discovery_clients: a list of DiscoveryClient - """ + def __init__(self, *discovery_clients: DiscoveryClient): self.__discovery_clients = discovery_clients - def get_instances(self, service_id): + def get_instances(self, service_id: str) -> List[ServiceInstance]: for client in self.__discovery_clients: services = client.get_instances(service_id) if not_none_nor_empty(services): @@ -29,5 +31,5 @@ def get_instances(self, service_id): return [] @property - def services(self): - return flat_map(lambda d: d.services, self.__discovery_clients) + def services(self) -> Set[str]: + return set(flat_map(lambda d: d.services, self.__discovery_clients)) diff --git a/commons/client/discovery/discovery_client.py b/commons/client/discovery/discovery_client.py index dc94e94..d65fa78 100644 --- a/commons/client/discovery/discovery_client.py +++ b/commons/client/discovery/discovery_client.py @@ -8,9 +8,10 @@ # standard library from abc import ABC, abstractmethod +from typing import List, Set # scip plugin -from commons.client.service_instance import StaticServiceInstance +from commons.client.service_instance import ServiceInstance, StaticServiceInstance from commons.utils.functional_operators import filter_get_first __author__ = "Waterball (johnny850807@gmail.com)" @@ -19,7 +20,7 @@ class DiscoveryClient(ABC): @abstractmethod - def get_instances(self, service_id): + def get_instances(self, service_id: str) -> List[ServiceInstance]: """ Gets all ServiceInstances associated with a particular serviceId. :param service_id: (str) The serviceId to query. @@ -29,7 +30,7 @@ def get_instances(self, service_id): @property @abstractmethod - def services(self): + def services(self) -> List[str]: """ :return: All known service IDs (*str). """ @@ -41,18 +42,18 @@ class StaticDiscoveryClient(DiscoveryClient): A DiscoveryClient initialized with the services. """ - def __init__(self, services): - self.__services = services + def __init__(self, instances: List[ServiceInstance]): + self.__instances = instances - def get_instances(self, service_id): - return list(filter(lambda s: s.service_id == service_id, self.services)) + def get_instances(self, service_id: str) -> List[ServiceInstance]: + return list(filter(lambda s: s.service_id == service_id, self.__instances)) @property - def services(self): - return self.__services + def services(self) -> Set[str]: + return {s.service_id for s in self.__instances} -def static_discovery_client(uri, service_id, instance_ids): +def static_discovery_client(uri: str, service_id: str, instance_ids: List[str]) -> StaticDiscoveryClient: """ A helper method that helps create a list of instances from the same service. :param uri the uri of every service's diff --git a/commons/client/service_instance.py b/commons/client/service_instance.py index 1d2f701..a7103e6 100644 --- a/commons/client/service_instance.py +++ b/commons/client/service_instance.py @@ -16,37 +16,37 @@ class ServiceInstance(ABC): @property @abstractmethod - def instance_id(self): + def instance_id(self) -> str: pass @property @abstractmethod - def service_id(self): + def service_id(self) -> str: pass @property @abstractmethod - def host(self): + def host(self) -> str: pass @property @abstractmethod - def port(self): + def port(self) -> int: pass @property @abstractmethod - def secure(self): + def secure(self) -> bool: pass @property @abstractmethod - def uri(self): + def uri(self) -> str: pass @property @abstractmethod - def scheme(self): + def scheme(self) -> str: pass @@ -55,7 +55,7 @@ class StaticServiceInstance(ServiceInstance): A service instance that is initialized with its basic properties """ - def __init__(self, uri, service_id, instance_id): + def __init__(self, uri: str, service_id: str, instance_id: str): """ :param uri: the url in the string type """ @@ -69,29 +69,29 @@ def __init__(self, uri, service_id, instance_id): self._instance_id = instance_id @property - def service_id(self): + def service_id(self) -> str: return self._service_id @property - def instance_id(self): + def instance_id(self) -> str: return self._instance_id @property - def host(self): + def host(self) -> str: return self._host @property - def port(self): + def port(self) -> int: return self._port @property - def secure(self): + def secure(self) -> bool: return self._secure @property - def uri(self): + def uri(self) -> str: return self._uri @property - def scheme(self): + def scheme(self) -> str: return self._scheme diff --git a/commons/utils/functional_operators.py b/commons/utils/functional_operators.py index 386d3da..78ac357 100644 --- a/commons/utils/functional_operators.py +++ b/commons/utils/functional_operators.py @@ -1,25 +1,24 @@ # -*- coding: utf-8 -*- +# standard library +from typing import Iterable __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" -def flat_map(f, xs): +def flat_map(f, xs: Iterable): """ :param f: a mapping function :param xs: (Iterable) :return: list """ - results = [] - for element in xs: - results += f(element) - return results + return [item for element in xs for item in f(element)] -def filter_get_first(f, xs): +def filter_get_first(f, xs: Iterable): """ :param f: a predicate function that returns a boolean - :param xs: (Iterable - :return: an element + :param xs: (Iterable) + :return: the first element matches the predicate """ - return next(filter(f, xs), None) + return [x for x in xs if f(x)][0] diff --git a/commons/utils/list_utils.py b/commons/utils/list_utils.py index 044008d..f9433f3 100644 --- a/commons/utils/list_utils.py +++ b/commons/utils/list_utils.py @@ -4,9 +4,8 @@ __license__ = "Apache 2.0" -def not_none_nor_empty(the_list): +def not_none_nor_empty(the_list: list): """ - :param the_list: a list :return: true if the list is neither none nor empty """ return the_list is not None and len(the_list) != 0 diff --git a/tests/client/discovery/composite_test.py b/tests/client/discovery/composite_test.py index f9eb46d..c1fbaed 100644 --- a/tests/client/discovery/composite_test.py +++ b/tests/client/discovery/composite_test.py @@ -16,17 +16,12 @@ def test_get_instances(): for service_id in range(1, 4): - instances = client.get_instances("service-{}".format(service_id)) + instances = client.get_instances(f"service-{service_id}") for instance_id in range(1, 4): instance = instances[instance_id - 1] - assert "{}-{}".format(service_id, instance_id) == instance.instance_id + assert f"{service_id}-{instance_id}" == instance.instance_id def test_get_services(): - instances = client.services - i = 0 - for service_id in range(1, 4): - for instance_id in range(1, 4): - assert "service-{}".format(service_id) == instances[i].service_id - assert "{}-{}".format(service_id, instance_id) == instances[i].instance_id - i += 1 + services = client.services + assert {"service-1", "service-2", "service-3"} == services From cdd9e197c88f61b8f138ab2097f7a0a36bf25715 Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 6 Nov 2020 20:41:38 +0800 Subject: [PATCH 22/24] improve filter_get_first utils --- commons/utils/functional_operators.py | 2 +- tests/utils/functional_operators_test.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/commons/utils/functional_operators.py b/commons/utils/functional_operators.py index 78ac357..9c04da7 100644 --- a/commons/utils/functional_operators.py +++ b/commons/utils/functional_operators.py @@ -21,4 +21,4 @@ def filter_get_first(f, xs: Iterable): :param xs: (Iterable) :return: the first element matches the predicate """ - return [x for x in xs if f(x)][0] + return next((x for x in xs if f(x)), None) diff --git a/tests/utils/functional_operators_test.py b/tests/utils/functional_operators_test.py index cf40349..8aa999f 100644 --- a/tests/utils/functional_operators_test.py +++ b/tests/utils/functional_operators_test.py @@ -11,7 +11,13 @@ def test_flat_map(): assert ["a", "b", "c", "1", "2", "3", ""] == results -def test_filter_get_first(): +def test_filter_get_first_Given_numbers(): the_list = [1, 2, 3, 4, 5, 6, 7, 8] result = filter_get_first(lambda e: e > 4, the_list) assert 5 == result + + +def test_filter_get_first_Given_empty_list(): + the_list = [] + result = filter_get_first(lambda e: e > 4, the_list) + assert result is None From bae830a74908752db1808b9a1a674e76f2be4612 Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Mon, 9 Nov 2020 11:43:10 +0800 Subject: [PATCH 23/24] :truck: Modify the package layout, rooted from spring_cloud --- context/__init__.py | 1 - {commons => spring_cloud}/__init__.py | 0 {commons/client => spring_cloud/commons}/__init__.py | 0 spring_cloud/commons/client/__init__.py | 3 +++ spring_cloud/commons/client/discovery/__init__.py | 4 ++++ .../commons}/client/discovery/composite.py | 8 ++++---- .../commons}/client/discovery/discovery_client.py | 3 +-- .../commons/client/loadbalancer}/__init__.py | 0 .../commons}/client/service_instance.py | 0 .../commons/client/service_registry}/__init__.py | 0 .../commons/utils}/__init__.py | 0 .../commons}/utils/functional_operators.py | 0 {commons => spring_cloud/commons}/utils/list_utils.py | 2 +- {commons/utils => spring_cloud/context}/__init__.py | 0 tests/__init__.py | 1 - tests/client/__init__.py | 1 - tests/client/discovery/__init__.py | 1 - .../{ => commons}/client/discovery/composite_test.py | 3 +-- .../{ => commons}/utils/functional_operators_test.py | 2 +- tests/commons/utils/list_utils_test.py | 11 +++++++++++ tests/utils/__init__.py | 2 -- 21 files changed, 26 insertions(+), 16 deletions(-) delete mode 100644 context/__init__.py rename {commons => spring_cloud}/__init__.py (100%) rename {commons/client => spring_cloud/commons}/__init__.py (100%) create mode 100644 spring_cloud/commons/client/__init__.py create mode 100644 spring_cloud/commons/client/discovery/__init__.py rename {commons => spring_cloud/commons}/client/discovery/composite.py (75%) rename {commons => spring_cloud/commons}/client/discovery/discovery_client.py (93%) rename {commons/client/discovery => spring_cloud/commons/client/loadbalancer}/__init__.py (100%) rename {commons => spring_cloud/commons}/client/service_instance.py (100%) rename {commons/client/loadbalancer => spring_cloud/commons/client/service_registry}/__init__.py (100%) rename {commons/client/service_registry => spring_cloud/commons/utils}/__init__.py (100%) rename {commons => spring_cloud/commons}/utils/functional_operators.py (100%) rename {commons => spring_cloud/commons}/utils/list_utils.py (79%) rename {commons/utils => spring_cloud/context}/__init__.py (100%) delete mode 100644 tests/__init__.py delete mode 100644 tests/client/__init__.py delete mode 100644 tests/client/discovery/__init__.py rename tests/{ => commons}/client/discovery/composite_test.py (84%) rename tests/{ => commons}/utils/functional_operators_test.py (86%) create mode 100644 tests/commons/utils/list_utils_test.py delete mode 100644 tests/utils/__init__.py diff --git a/context/__init__.py b/context/__init__.py deleted file mode 100644 index 40a96af..0000000 --- a/context/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/commons/__init__.py b/spring_cloud/__init__.py similarity index 100% rename from commons/__init__.py rename to spring_cloud/__init__.py diff --git a/commons/client/__init__.py b/spring_cloud/commons/__init__.py similarity index 100% rename from commons/client/__init__.py rename to spring_cloud/commons/__init__.py diff --git a/spring_cloud/commons/client/__init__.py b/spring_cloud/commons/client/__init__.py new file mode 100644 index 0000000..cdeb409 --- /dev/null +++ b/spring_cloud/commons/client/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from .service_instance import * diff --git a/spring_cloud/commons/client/discovery/__init__.py b/spring_cloud/commons/client/discovery/__init__.py new file mode 100644 index 0000000..5d5a70e --- /dev/null +++ b/spring_cloud/commons/client/discovery/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from .composite import * +from .discovery_client import * diff --git a/commons/client/discovery/composite.py b/spring_cloud/commons/client/discovery/composite.py similarity index 75% rename from commons/client/discovery/composite.py rename to spring_cloud/commons/client/discovery/composite.py index 5541ec0..39ad2f4 100644 --- a/commons/client/discovery/composite.py +++ b/spring_cloud/commons/client/discovery/composite.py @@ -3,15 +3,15 @@ from typing import List, Set # scip plugin -from commons.client.service_instance import ServiceInstance +from spring_cloud.commons.client import ServiceInstance __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" # scip plugin -from commons.client.discovery.discovery_client import DiscoveryClient -from commons.utils.functional_operators import flat_map -from commons.utils.list_utils import not_none_nor_empty +from spring_cloud.commons.client.discovery.discovery_client import DiscoveryClient +from spring_cloud.commons.utils.functional_operators import flat_map +from spring_cloud.commons.utils.list_utils import not_none_nor_empty class CompositeDiscoveryClient(DiscoveryClient): diff --git a/commons/client/discovery/discovery_client.py b/spring_cloud/commons/client/discovery/discovery_client.py similarity index 93% rename from commons/client/discovery/discovery_client.py rename to spring_cloud/commons/client/discovery/discovery_client.py index d65fa78..a335b71 100644 --- a/commons/client/discovery/discovery_client.py +++ b/spring_cloud/commons/client/discovery/discovery_client.py @@ -11,8 +11,7 @@ from typing import List, Set # scip plugin -from commons.client.service_instance import ServiceInstance, StaticServiceInstance -from commons.utils.functional_operators import filter_get_first +from spring_cloud.commons.client import ServiceInstance, StaticServiceInstance __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" diff --git a/commons/client/discovery/__init__.py b/spring_cloud/commons/client/loadbalancer/__init__.py similarity index 100% rename from commons/client/discovery/__init__.py rename to spring_cloud/commons/client/loadbalancer/__init__.py diff --git a/commons/client/service_instance.py b/spring_cloud/commons/client/service_instance.py similarity index 100% rename from commons/client/service_instance.py rename to spring_cloud/commons/client/service_instance.py diff --git a/commons/client/loadbalancer/__init__.py b/spring_cloud/commons/client/service_registry/__init__.py similarity index 100% rename from commons/client/loadbalancer/__init__.py rename to spring_cloud/commons/client/service_registry/__init__.py diff --git a/commons/client/service_registry/__init__.py b/spring_cloud/commons/utils/__init__.py similarity index 100% rename from commons/client/service_registry/__init__.py rename to spring_cloud/commons/utils/__init__.py diff --git a/commons/utils/functional_operators.py b/spring_cloud/commons/utils/functional_operators.py similarity index 100% rename from commons/utils/functional_operators.py rename to spring_cloud/commons/utils/functional_operators.py diff --git a/commons/utils/list_utils.py b/spring_cloud/commons/utils/list_utils.py similarity index 79% rename from commons/utils/list_utils.py rename to spring_cloud/commons/utils/list_utils.py index f9433f3..507af58 100644 --- a/commons/utils/list_utils.py +++ b/spring_cloud/commons/utils/list_utils.py @@ -8,4 +8,4 @@ def not_none_nor_empty(the_list: list): """ :return: true if the list is neither none nor empty """ - return the_list is not None and len(the_list) != 0 + return isinstance(the_list, list) and the_list diff --git a/commons/utils/__init__.py b/spring_cloud/context/__init__.py similarity index 100% rename from commons/utils/__init__.py rename to spring_cloud/context/__init__.py diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index 40a96af..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/tests/client/__init__.py b/tests/client/__init__.py deleted file mode 100644 index 40a96af..0000000 --- a/tests/client/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/tests/client/discovery/__init__.py b/tests/client/discovery/__init__.py deleted file mode 100644 index 40a96af..0000000 --- a/tests/client/discovery/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/tests/client/discovery/composite_test.py b/tests/commons/client/discovery/composite_test.py similarity index 84% rename from tests/client/discovery/composite_test.py rename to tests/commons/client/discovery/composite_test.py index c1fbaed..1dd5f24 100644 --- a/tests/client/discovery/composite_test.py +++ b/tests/commons/client/discovery/composite_test.py @@ -4,8 +4,7 @@ __license__ = "Apache 2.0" # scip plugin -from commons.client.discovery.composite import CompositeDiscoveryClient -from commons.client.discovery.discovery_client import static_discovery_client +from spring_cloud.commons.client.discovery import CompositeDiscoveryClient, static_discovery_client client = CompositeDiscoveryClient( static_discovery_client("url-1", "service-1", ["1-1", "1-2", "1-3"]), diff --git a/tests/utils/functional_operators_test.py b/tests/commons/utils/functional_operators_test.py similarity index 86% rename from tests/utils/functional_operators_test.py rename to tests/commons/utils/functional_operators_test.py index 8aa999f..199ac13 100644 --- a/tests/utils/functional_operators_test.py +++ b/tests/commons/utils/functional_operators_test.py @@ -1,5 +1,5 @@ # scip plugin -from commons.utils.functional_operators import filter_get_first, flat_map +from spring_cloud.commons.utils.functional_operators import filter_get_first, flat_map __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" diff --git a/tests/commons/utils/list_utils_test.py b/tests/commons/utils/list_utils_test.py new file mode 100644 index 0000000..ef7c209 --- /dev/null +++ b/tests/commons/utils/list_utils_test.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# scip plugin +from spring_cloud.commons.utils.list_utils import not_none_nor_empty + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +def test_not_none_nor_empty(): + assert not_none_nor_empty([1]) + assert not not_none_nor_empty([]) diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py deleted file mode 100644 index d7b1237..0000000 --- a/tests/utils/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -__author__ = "Waterball (johnny850807@gmail.com)" -__license__ = "Apache 2.0" From 2718ef0794f23a51c28bd7a9f00e03c35439c7a9 Mon Sep 17 00:00:00 2001 From: johnny850807 Date: Fri, 13 Nov 2020 00:58:43 +0800 Subject: [PATCH 24/24] add utils/validate --- .../client/loadbalancer/supplier/decorator.py | 4 ++-- spring_cloud/commons/utils/validate.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 spring_cloud/commons/utils/validate.py diff --git a/spring_cloud/commons/client/loadbalancer/supplier/decorator.py b/spring_cloud/commons/client/loadbalancer/supplier/decorator.py index 8b6da37..8d2e564 100644 --- a/spring_cloud/commons/client/loadbalancer/supplier/decorator.py +++ b/spring_cloud/commons/client/loadbalancer/supplier/decorator.py @@ -5,6 +5,7 @@ # scip plugin from spring_cloud.commons.client import ServiceInstance from spring_cloud.commons.exceptions import NoneTypeError +from spring_cloud.commons.utils import validate __author__ = "Waterball (johnny850807@gmail.com)" __license__ = "Apache 2.0" @@ -24,8 +25,7 @@ class DelegatingServiceInstanceListSupplier(ServiceInstanceListSupplier, ABC): """ def __init__(self, delegate: ServiceInstanceListSupplier): - NoneTypeError.raise_if_none(delegate) - self.delegate = delegate + self.delegate = validate.not_none(delegate) @property def service_id(self) -> str: diff --git a/spring_cloud/commons/utils/validate.py b/spring_cloud/commons/utils/validate.py new file mode 100644 index 0000000..e1e0a8d --- /dev/null +++ b/spring_cloud/commons/utils/validate.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# scip plugin +from spring_cloud.commons.exceptions.primitive import NoneTypeError + +__author__ = "Waterball (johnny850807@gmail.com)" +__license__ = "Apache 2.0" + + +def not_none(obj): + if obj: + return obj + raise NoneTypeError + + +def is_instance_of(obj, the_type): + if isinstance(obj, the_type): + return obj + raise TypeError()